diff --git a/app/build.gradle b/app/build.gradle index c78c327..6284311 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,6 +21,7 @@ android { versionCode 11 versionName "1.0-beta11" jackOptions.enabled = false + multiDexEnabled true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 @@ -38,11 +39,14 @@ android { //ext.jabitVersion = '2.0.4' ext.jabitVersion = 'feature-extended-encoding-SNAPSHOT' +ext.supportVersion = '25.3.1' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:25.1.0' - compile 'com.android.support:support-v4:25.1.0' - compile 'com.android.support:design:25.1.0' + + compile "com.android.support:appcompat-v7:$supportVersion" + compile "com.android.support:support-v4:$supportVersion" + compile "com.android.support:design:$supportVersion" + compile "com.android.support:multidex:1.0.1" compile "ch.dissem.jabit:jabit-core:$jabitVersion" compile "ch.dissem.jabit:jabit-networking:$jabitVersion" @@ -50,30 +54,32 @@ dependencies { compile "ch.dissem.jabit:jabit-extensions:$jabitVersion" compile "ch.dissem.jabit:jabit-wif:$jabitVersion" - compile 'org.slf4j:slf4j-android:1.7.12' + compile 'org.slf4j:slf4j-android:1.7.25' - compile 'com.mikepenz:materialize:1.0.0@aar' - compile('com.mikepenz:materialdrawer:5.6.0@aar') { + compile 'com.mikepenz:materialize:1.0.1@aar' + compile('com.mikepenz:materialdrawer:5.9.0@aar') { transitive = true } - compile('com.mikepenz:aboutlibraries:5.8.1@aar') { + compile('com.mikepenz:aboutlibraries:5.9.5@aar') { transitive = true } - compile 'com.mikepenz:iconics:1.6.2@aar' - compile 'com.mikepenz:community-material-typeface:1.5.54.2@aar' + compile "com.mikepenz:iconics-core:2.8.3@aar" + compile 'com.mikepenz:google-material-typeface:3.0.1.0.original@aar' + compile 'com.mikepenz:community-material-typeface:1.9.32.1@aar' - compile 'com.journeyapps:zxing-android-embedded:3.3.0@aar' + compile 'com.journeyapps:zxing-android-embedded:3.5.0@aar' compile 'com.google.zxing:core:3.3.0' + compile 'io.github.yavski:fab-speed-dial:1.0.6' compile 'com.github.amlcurran.showcaseview:library:5.4.3' - compile('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.9.3@aar') { + compile('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.10.4@aar') { transitive = true } - compile 'com.github.angads25:filepicker:1.0.6' + compile 'com.github.angads25:filepicker:1.1.0' + compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:1.10.19' - compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4' + testCompile 'org.mockito:mockito-core:2.7.22' } idea.module { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a7a2f74..d46c295 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" + android:name="android.support.multidex.MultiDexApplication" tools:replace="android:allowBackup"> find(String where) { List result = new LinkedList<>(); @@ -205,7 +252,8 @@ public class AndroidMessageRepository extends AbstractMessageRepository { COLUMN_STATUS, COLUMN_TTL, COLUMN_RETRIES, - COLUMN_NEXT_TRY + COLUMN_NEXT_TRY, + COLUMN_CONVERSATION }; SQLiteDatabase db = sql.getReadableDatabase(); @@ -220,8 +268,8 @@ public class AndroidMessageRepository extends AbstractMessageRepository { byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA)); Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex (COLUMN_TYPE))); - Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new - ByteArrayInputStream(data)); + Plaintext.Builder builder = Plaintext.readWithoutSignature(type, + new ByteArrayInputStream(data)); long id = c.getLong(c.getColumnIndex(COLUMN_ID)); builder.id(id); builder.IV(new InventoryVector(iv)); @@ -240,6 +288,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository { if (!c.isNull(nextTryColumn)) { builder.nextTry(c.getLong(nextTryColumn)); } + builder.conversation(asUuid(c.getBlob(c.getColumnIndex(COLUMN_CONVERSATION)))); builder.labels(findLabels(id)); result.add(builder.build()); } @@ -268,6 +317,8 @@ public class AndroidMessageRepository extends AbstractMessageRepository { update(db, message); } + updateParents(db, message); + // remove existing labels db.delete(JOIN_TABLE_NAME, "message_id=?", new String[]{valueOf(message.getId())}); @@ -302,6 +353,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository { values.put(COLUMN_TTL, message.getTTL()); values.put(COLUMN_RETRIES, message.getRetries()); values.put(COLUMN_NEXT_TRY, message.getNextTry()); + values.put(COLUMN_CONVERSATION, UuidUtils.asBytes(message.getConversationId())); long id = db.insertOrThrow(TABLE_NAME, null, values); message.setId(id); } @@ -322,6 +374,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository { values.put(COLUMN_TTL, message.getTTL()); values.put(COLUMN_RETRIES, message.getRetries()); values.put(COLUMN_NEXT_TRY, message.getNextTry()); + values.put(COLUMN_CONVERSATION, UuidUtils.asBytes(message.getConversationId())); db.update(TABLE_NAME, values, "id = " + message.getId(), null); } diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java index b20c173..2183fcb 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java +++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidNodeRegistry.java @@ -10,6 +10,7 @@ import android.database.sqlite.SQLiteStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -56,6 +57,12 @@ public class AndroidNodeRegistry implements NodeRegistry { db.delete(TABLE_NAME, "time < ?", new String[]{valueOf(now(-28 * DAY))}); } + @Override + public void clear() { + SQLiteDatabase db = sql.getWritableDatabase(); + db.delete(TABLE_NAME, null, null); + } + private Long loadExistingTime(NetworkAddress node) { SQLiteStatement statement = loadExistingStatement.get(); if (statement == null) { diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java b/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java index 0e95170..db7d411 100644 --- a/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java +++ b/app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.java @@ -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 = 6; + private static final int DATABASE_VERSION = 7; private static final String DATABASE_NAME = "jabit.db"; private final Context ctx; @@ -61,6 +61,8 @@ public class SqlHelper extends SQLiteOpenHelper { executeMigration(db, "V3.3__Create_table_node"); case 5: executeMigration(db, "V3.4__Add_label_outbox"); + case 6: + executeMigration(db, "V4.0__Create_table_message_parent"); default: // Nothing to do. Let's assume we won't upgrade from a version that's newer than // DATABASE_VERSION. @@ -73,7 +75,7 @@ public class SqlHelper extends SQLiteOpenHelper { } } - public static StringBuilder join(long... numbers) { + static StringBuilder join(long... numbers) { StringBuilder streamList = new StringBuilder(); for (int i = 0; i < numbers.length; i++) { if (i > 0) streamList.append(", "); @@ -82,7 +84,7 @@ public class SqlHelper extends SQLiteOpenHelper { return streamList; } - public static StringBuilder join(Enum<?>... types) { + static StringBuilder join(Enum<?>... types) { StringBuilder streamList = new StringBuilder(); for (int i = 0; i < types.length; i++) { if (i > 0) streamList.append(", "); diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java index 5c8a528..ef64b61 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java +++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java @@ -18,6 +18,7 @@ package ch.dissem.apps.abit.service; import android.app.Service; import android.content.Intent; +import android.os.Handler; import android.os.IBinder; import android.support.annotation.Nullable; @@ -38,6 +39,17 @@ public class BitmessageService extends Service { private NetworkNotification notification = null; + private final Handler cleanupHandler = new Handler(); + private final Runnable cleanupTask = new Runnable() { + @Override + public void run() { + bmc.cleanup(); + if (isRunning()) { + cleanupHandler.postDelayed(this, 24 * 60 * 60 * 1000L); + } + } + }; + public static boolean isRunning() { return running && bmc.isRunning(); } @@ -61,6 +73,7 @@ public class BitmessageService extends Service { bmc.startup(); } notification.show(); + cleanupHandler.postDelayed(cleanupTask, 24 * 60 * 60 * 1000L); } return Service.START_STICKY; } @@ -72,6 +85,8 @@ public class BitmessageService extends Service { } running = false; notification.showShutdown(); + cleanupHandler.removeCallbacks(cleanupTask); + bmc.cleanup(); stopSelf(); } diff --git a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java index 1bceb0b..e01f099 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java +++ b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java @@ -42,6 +42,7 @@ import ch.dissem.bitmessage.networking.nio.NioNetworkHandler; import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.ProofOfWorkRepository; +import ch.dissem.bitmessage.utils.ConversationService; import ch.dissem.bitmessage.utils.TTL; import static ch.dissem.bitmessage.utils.UnixTime.DAY; @@ -51,6 +52,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY; */ public class Singleton { private static BitmessageContext bitmessageContext; + private static ConversationService conversationService; private static MessageListener messageListener; private static BitmessageAddress identity; private static AndroidProofOfWorkRepository powRepo; @@ -160,4 +162,16 @@ public class Singleton { throw new IllegalArgumentException("Identity expected, but no private key available"); Singleton.identity = identity; } + + public static ConversationService getConversationService(Context ctx) { + if (conversationService == null) { + final BitmessageContext bmc = getBitmessageContext(ctx); + synchronized (Singleton.class) { + if (conversationService == null) { + conversationService = new ConversationService(bmc.messages()); + } + } + } + return conversationService; + } } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Drawables.java b/app/src/main/java/ch/dissem/apps/abit/util/Drawables.java index a35281a..dfda9c8 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/Drawables.java +++ b/app/src/main/java/ch/dissem/apps/abit/util/Drawables.java @@ -19,19 +19,41 @@ package ch.dissem.apps.abit.util; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.util.Base64; import android.view.Menu; import android.view.MenuItem; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.typeface.IIcon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + import ch.dissem.apps.abit.Identicon; import ch.dissem.apps.abit.R; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.exception.ApplicationException; + +import static android.graphics.Color.BLACK; +import static android.graphics.Color.WHITE; +import static android.util.Base64.NO_WRAP; +import static android.util.Base64.URL_SAFE; /** * Some helper methods to work with drawables. */ public class Drawables { + private static final Logger LOG = LoggerFactory.getLogger(Drawables.class); + + private static final int QR_CODE_SIZE = 350; + public static MenuItem addIcon(Context ctx, Menu menu, int menuItem, IIcon icon) { MenuItem item = menu.findItem(menuItem); item.setIcon(new IconicsDrawable(ctx, icon).colorRes(R.color.colorPrimaryDarkText).actionBar()); @@ -49,4 +71,42 @@ public class Drawables { identicon.draw(canvas); return bitmap; } + + public static Bitmap qrCode(BitmessageAddress address) { + StringBuilder link = new StringBuilder("bitmessage:"); + link.append(address.getAddress()); + if (address.getAlias() != null) { + link.append("?label=").append(address.getAlias()); + } + if (address.getPubkey() != null) { + link.append(address.getAlias() == null ? '?' : '&'); + ByteArrayOutputStream pubkey = new ByteArrayOutputStream(); + try { + address.getPubkey().writeUnencrypted(pubkey); + } catch (IOException e) { + throw new ApplicationException(e); + } + link.append("pubkey=").append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE | NO_WRAP)); + } + BitMatrix result; + try { + result = new MultiFormatWriter().encode(link.toString(), + BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE, null); + } catch (WriterException e) { + LOG.error(e.getMessage(), e); + return null; + } + int w = result.getWidth(); + int h = result.getHeight(); + int[] pixels = new int[w * h]; + for (int y = 0; y < h; y++) { + int offset = y * w; + for (int x = 0; x < w; x++) { + pixels[offset + x] = result.get(x, y) ? BLACK : WHITE; + } + } + Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, QR_CODE_SIZE, 0, 0, w, h); + return bitmap; + } } diff --git a/app/src/main/java/ch/dissem/apps/abit/util/Labels.java b/app/src/main/java/ch/dissem/apps/abit/util/Labels.java new file mode 100644 index 0000000..75c6def --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/util/Labels.java @@ -0,0 +1,77 @@ +package ch.dissem.apps.abit.util; + +import android.content.Context; +import android.support.annotation.ColorInt; + +import com.mikepenz.community_material_typeface_library.CommunityMaterial; +import com.mikepenz.google_material_typeface_library.GoogleMaterial; +import com.mikepenz.iconics.typeface.IIcon; + +import ch.dissem.apps.abit.R; +import ch.dissem.bitmessage.entity.valueobject.Label; + +/** + * Helper class to help with translating the default labels, getting label colors and so on. + */ +public class Labels { + public static String getText(Label label, Context ctx) { + return getText(label.getType(), label.toString(), ctx); + } + + public static String getText(Label.Type type, String alternative, Context ctx) { + if (type == null) { + return alternative; + } else { + switch (type) { + case INBOX: + return ctx.getString(R.string.inbox); + case DRAFT: + return ctx.getString(R.string.draft); + case OUTBOX: + return ctx.getString(R.string.outbox); + case SENT: + return ctx.getString(R.string.sent); + case UNREAD: + return ctx.getString(R.string.unread); + case TRASH: + return ctx.getString(R.string.trash); + case BROADCAST: + return ctx.getString(R.string.broadcasts); + default: + return alternative; + } + } + } + + public static IIcon getIcon(Label label) { + if (label.getType() == null) { + return CommunityMaterial.Icon.cmd_label; + } + switch (label.getType()) { + case INBOX: + return GoogleMaterial.Icon.gmd_inbox; + case DRAFT: + return CommunityMaterial.Icon.cmd_file; + case OUTBOX: + return CommunityMaterial.Icon.cmd_inbox_arrow_up; + case SENT: + return CommunityMaterial.Icon.cmd_send; + case BROADCAST: + return CommunityMaterial.Icon.cmd_rss; + case UNREAD: + return GoogleMaterial.Icon.gmd_markunread_mailbox; + case TRASH: + return GoogleMaterial.Icon.gmd_delete; + default: + return CommunityMaterial.Icon.cmd_label; + } + } + + @ColorInt + public static int getColor(Label label) { + if (label.getType() == null) { + return label.getColor(); + } + return 0xFF000000; + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/util/UuidUtils.java b/app/src/main/java/ch/dissem/apps/abit/util/UuidUtils.java new file mode 100644 index 0000000..543dbac --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/util/UuidUtils.java @@ -0,0 +1,37 @@ +package ch.dissem.apps.abit.util; + +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * SQLite has no UUID data type, and UUIDs are therefore best saved as BINARY[16]. This class + * takes care of conversion between byte[16] and UUID. + * <p> + * Thanks to Brice Roncace on + * <a href="http://stackoverflow.com/questions/17893609/convert-uuid-to-byte-that-works-when-using-uuid-nameuuidfrombytesb"> + * Stack Overflow + * </a> + * for providing the UUID <-> byte[] conversions. + * </p> + */ +public class UuidUtils { + public static UUID asUuid(byte[] bytes) { + if (bytes == null) { + return null; + } + ByteBuffer bb = ByteBuffer.wrap(bytes); + long firstLong = bb.getLong(); + long secondLong = bb.getLong(); + return new UUID(firstLong, secondLong); + } + + public static byte[] asBytes(UUID uuid) { + if (uuid == null) { + return null; + } + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return bb.array(); + } +} diff --git a/app/src/main/res/drawable/border_bottom.xml b/app/src/main/res/drawable/border_bottom.xml new file mode 100644 index 0000000..ab52873 --- /dev/null +++ b/app/src/main/res/drawable/border_bottom.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <item android:top="1dp" android:bottom="1dp"> + <shape + android:shape="rectangle"> + <stroke android:width="1dp" android:color="#FFDDDDDD" /> + <solid android:color="#00000000" /> + </shape> + </item> +</layer-list> diff --git a/app/src/main/res/layout/dialog_select_message_encoding.xml b/app/src/main/res/layout/dialog_select_message_encoding.xml index 4a1991f..02dcab0 100644 --- a/app/src/main/res/layout/dialog_select_message_encoding.xml +++ b/app/src/main/res/layout/dialog_select_message_encoding.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> -<android.support.constraint.ConstraintLayout +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" @@ -31,10 +31,9 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:text="@string/select_encoding_warning" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:layout_constraintLeft_creator="1" - tools:layout_constraintTop_creator="1"/> + android:layout_alignParentTop="true" + android:layout_alignParentStart="true" + android:layout_alignParentEnd="true"/> <RadioGroup @@ -43,9 +42,9 @@ android:layout_height="wrap_content" android:paddingBottom="24dp" android:paddingTop="24dp" - app:layout_constraintLeft_toLeftOf="@+id/description" - app:layout_constraintRight_toRightOf="@+id/description" - app:layout_constraintTop_toBottomOf="@+id/description"> + android:layout_alignStart="@+id/description" + android:layout_alignEnd="@+id/description" + android:layout_below="@+id/description"> <RadioButton android:id="@+id/simple" @@ -72,10 +71,8 @@ android:text="@string/ok" android:textColor="@color/colorAccent" app:layout_constraintHorizontal_bias="1.0" - app:layout_constraintRight_toRightOf="@+id/radioGroup" - app:layout_constraintTop_toBottomOf="@+id/radioGroup" - tools:layout_constraintRight_creator="1" - tools:layout_constraintTop_creator="1"/> + android:layout_alignRight="@+id/radioGroup" + android:layout_below="@+id/radioGroup"/> <Button android:id="@+id/dismiss" @@ -84,7 +81,7 @@ android:layout_height="wrap_content" android:text="@string/cancel" android:textColor="@color/colorAccent" - app:layout_constraintRight_toLeftOf="@+id/ok" - app:layout_constraintTop_toBottomOf="@+id/radioGroup"/> + android:layout_toLeftOf="@+id/ok" + android:layout_below="@+id/radioGroup"/> -</android.support.constraint.ConstraintLayout> +</RelativeLayout> diff --git a/app/src/main/res/layout/fragment_message_detail.xml b/app/src/main/res/layout/fragment_message_detail.xml index b0a7ef5..73bb3d9 100644 --- a/app/src/main/res/layout/fragment_message_detail.xml +++ b/app/src/main/res/layout/fragment_message_detail.xml @@ -1,14 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <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"> + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="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:paddingBottom="64dp" android:orientation="vertical"> <TextView @@ -24,7 +25,7 @@ android:padding="16dp" android:textAppearance="?android:attr/textAppearanceLarge" tools:ignore="UnusedAttribute" - tools:text="Subject"/> + tools:text="Subject" /> <ImageView android:id="@+id/status" @@ -32,17 +33,17 @@ 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"/> + android:tint="@color/colorAccent" + tools:ignore="ContentDescription" + tools:src="@drawable/ic_notification_proof_of_work" /> <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" @@ -52,7 +53,7 @@ android:layout_below="@+id/divider" android:layout_margin="16dp" android:src="@color/colorAccent" - tools:ignore="ContentDescription"/> + tools:ignore="ContentDescription" /> <TextView android:id="@+id/sender" @@ -64,7 +65,7 @@ android:paddingLeft="8dp" android:paddingRight="8dp" android:textStyle="bold" - tools:text="Sender"/> + tools:text="Sender" /> <TextView android:id="@+id/recipient" @@ -75,19 +76,44 @@ android:gravity="center_vertical" android:paddingLeft="8dp" android:paddingRight="8dp" - tools:text="Recipient"/> + tools:text="Recipient" /> + + <android.support.v7.widget.RecyclerView + android:id="@+id/parents" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/avatar" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" /> <TextView android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentStart="true" - android:layout_below="@+id/avatar" + android:layout_below="@+id/parents" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginTop="32dp" - android:paddingBottom="64dp" - android:text="New Text" - android:textIsSelectable="true"/> + android:paddingBottom="16dp" + tools:text="Message Body" + android:textIsSelectable="true" /> + + <android.support.v7.widget.RecyclerView + android:id="@+id/labels" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/text" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="16dp"/> + + <android.support.v7.widget.RecyclerView + android:id="@+id/responses" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/labels" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" /> </RelativeLayout> </ScrollView> diff --git a/app/src/main/res/layout/item_label.xml b/app/src/main/res/layout/item_label.xml new file mode 100644 index 0000000..26869a5 --- /dev/null +++ b/app/src/main/res/layout/item_label.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + + <com.mikepenz.iconics.view.IconicsImageView + android:id="@+id/icon" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + app:iiv_color="@android:color/black" + app:iiv_icon="cmd-label" /> + + <TextView + android:id="@+id/label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="8dp" + android:paddingEnd="24dp" + tools:text="Label" + android:layout_alignParentTop="true" + android:layout_toEndOf="@+id/icon" /> +</RelativeLayout> diff --git a/app/src/main/res/layout/item_message_minimized.xml b/app/src/main/res/layout/item_message_minimized.xml new file mode 100644 index 0000000..0b2e209 --- /dev/null +++ b/app/src/main/res/layout/item_message_minimized.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + android:background="@drawable/border_bottom"> + + <ImageView + android:id="@+id/avatar" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_marginTop="5dp" + android:src="@color/colorPrimaryDark" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/sender" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentTop="true" + android:layout_toEndOf="@+id/avatar" + android:ellipsize="end" + android:lines="1" + android:paddingBottom="0dp" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:textStyle="bold" + tools:text="Sender" /> + + <TextView + android:id="@+id/text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_below="@+id/sender" + android:layout_toEndOf="@+id/avatar" + android:ellipsize="end" + android:gravity="center_vertical" + android:lines="1" + android:paddingBottom="8dp" + android:paddingStart="8dp" + android:paddingEnd="8dp" + 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> diff --git a/app/src/main/res/layout/message_row.xml b/app/src/main/res/layout/message_row.xml index f23d5c1..9c9f88a 100644 --- a/app/src/main/res/layout/message_row.xml +++ b/app/src/main/res/layout/message_row.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- +<?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright 2015 Christian Basler ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +14,7 @@ ~ limitations under the License. --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -31,11 +29,10 @@ android:foreground="?attr/selectableItemBackground" tools:ignore="UselessParent"> - <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground"> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground"> <ImageView android:id="@+id/avatar" @@ -45,7 +42,7 @@ android:layout_alignParentTop="true" android:layout_margin="16dp" android:src="@color/colorPrimaryDark" - tools:ignore="ContentDescription"/> + tools:ignore="ContentDescription" /> <TextView android:id="@+id/sender" @@ -63,8 +60,7 @@ android:paddingTop="0dp" android:textAppearance="?android:attr/textAppearanceMedium" android:textStyle="bold" - tools:text="Sender" - /> + tools:text="Sender" /> <TextView android:id="@+id/subject" @@ -78,7 +74,7 @@ android:paddingLeft="8dp" android:paddingRight="8dp" android:textAppearance="?android:attr/textAppearanceSmall" - tools:text="Subject"/> + tools:text="Subject" /> <TextView android:id="@+id/text" @@ -94,7 +90,7 @@ android:paddingLeft="8dp" android:paddingRight="8dp" android:textAppearance="?android:attr/textAppearanceSmall" - tools:text="Text"/> + tools:text="Text" /> <ImageView android:id="@+id/status" @@ -106,7 +102,7 @@ android:layout_marginEnd="-8dp" android:tint="@color/colorAccent" tools:ignore="ContentDescription" - tools:src="@drawable/ic_notification_proof_of_work"/> + tools:src="@drawable/ic_notification_proof_of_work" /> </RelativeLayout> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ba7dc9e..7d6f486 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -19,7 +19,7 @@ <item name="android:textColor">@color/colorAccent</item> </style> - <style name="FixedDialog" parent="Theme.AppCompat.Light.Dialog"> + <style name="FixedDialog" parent="Theme.AppCompat.Light.Dialog.MinWidth"> <item name="windowNoTitle">false</item> </style> </resources> diff --git a/build.gradle b/build.gradle index 149d3e7..ceae43c 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.14.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -17,6 +18,8 @@ buildscript { } allprojects { + apply plugin: 'com.github.ben-manes.versions' + repositories { jcenter() mavenCentral() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 3baa851..9b92f4a 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e50d996..8164845 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Aug 12 22:10:25 CEST 2016 +#Tue Apr 04 00:02:32 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/gradlew b/gradlew index 27309d9..4453cce 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f6d5974..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line