Merge branch 'feature/conversations' into develop
This commit is contained in:
commit
3ae572bcf2
@ -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 {
|
||||
|
@ -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">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
@ -0,0 +1,11 @@
|
||||
ALTER TABLE Message ADD COLUMN conversation BINARY[16];
|
||||
|
||||
CREATE TABLE Message_Parent (
|
||||
parent BINARY(64) NOT NULL,
|
||||
child BINARY(64) NOT NULL,
|
||||
pos INT NOT NULL,
|
||||
conversation BINARY[16] NOT NULL,
|
||||
|
||||
PRIMARY KEY (parent, child),
|
||||
FOREIGN KEY (child) REFERENCES Message (iv)
|
||||
);
|
@ -20,7 +20,6 @@ import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
@ -38,24 +37,14 @@ import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.apps.abit.util.Drawables;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.wif.WifExporter;
|
||||
|
||||
import static android.graphics.Color.BLACK;
|
||||
import static android.graphics.Color.WHITE;
|
||||
|
||||
|
||||
/**
|
||||
* A fragment representing a single Message detail screen.
|
||||
@ -64,7 +53,6 @@ import static android.graphics.Color.WHITE;
|
||||
* on handsets.
|
||||
*/
|
||||
public class AddressDetailFragment extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AddressDetailFragment.class);
|
||||
/**
|
||||
* The fragment argument representing the item ID that this fragment
|
||||
* represents.
|
||||
@ -72,8 +60,6 @@ public class AddressDetailFragment extends Fragment {
|
||||
public static final String ARG_ITEM = "item";
|
||||
public static final String EXPORT_POSTFIX = ".keys.dat";
|
||||
|
||||
private static final int QR_CODE_SIZE = 350;
|
||||
|
||||
/**
|
||||
* The content this fragment is presenting.
|
||||
*/
|
||||
@ -257,40 +243,12 @@ public class AddressDetailFragment extends Fragment {
|
||||
|
||||
// QR code
|
||||
ImageView qrCode = (ImageView) rootView.findViewById(R.id.qr_code);
|
||||
qrCode.setImageBitmap(qrCode(item));
|
||||
qrCode.setImageBitmap(Drawables.qrCode(item));
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
Bitmap qrCode(BitmessageAddress address) {
|
||||
StringBuilder link = new StringBuilder("bitmessage:");
|
||||
link.append(address.getAddress());
|
||||
if (address.getAlias() != null) {
|
||||
link.append("?label=").append(address.getAlias());
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (item != null) {
|
||||
|
@ -87,8 +87,8 @@ public class ComposeMessageActivity extends AppCompatActivity {
|
||||
// so features like threading can be supported
|
||||
if (item.getEncoding() == EXTENDED) {
|
||||
replyIntent.putExtra(EXTRA_ENCODING, EXTENDED);
|
||||
replyIntent.putExtra(EXTRA_PARENT, item);
|
||||
}
|
||||
replyIntent.putExtra(EXTRA_PARENT, item);
|
||||
String prefix;
|
||||
if (item.getSubject().length() >= 3 && item.getSubject().substring(0, 3)
|
||||
.equalsIgnoreCase("RE:")) {
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
@ -79,6 +80,11 @@ public class ComposeMessageFragment extends Fragment {
|
||||
if (getArguments() != null) {
|
||||
if (getArguments().containsKey(EXTRA_IDENTITY)) {
|
||||
identity = (BitmessageAddress) getArguments().getSerializable(EXTRA_IDENTITY);
|
||||
if (getActivity() != null) {
|
||||
if (identity == null || identity.getPrivateKey() == null) {
|
||||
identity = Singleton.getIdentity(getActivity());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("No identity set for ComposeMessageFragment");
|
||||
}
|
||||
@ -156,6 +162,14 @@ public class ComposeMessageFragment extends Fragment {
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (identity == null || identity.getPrivateKey() == null) {
|
||||
identity = Singleton.getIdentity(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.compose, menu);
|
||||
@ -170,6 +184,9 @@ public class ComposeMessageFragment extends Fragment {
|
||||
return true;
|
||||
case R.id.select_encoding:
|
||||
SelectEncodingDialogFragment encodingDialog = new SelectEncodingDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(EXTRA_ENCODING, encoding);
|
||||
encodingDialog.setArguments(args);
|
||||
encodingDialog.setTargetFragment(this, 0);
|
||||
encodingDialog.show(getFragmentManager(), "select encoding dialog");
|
||||
return true;
|
||||
|
@ -20,17 +20,32 @@ import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Base64;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V2Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
|
||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
||||
|
||||
import static android.util.Base64.URL_SAFE;
|
||||
|
||||
public class CreateAddressActivity extends AppCompatActivity {
|
||||
private static final Pattern KEY_VALUE_PATTERN = Pattern.compile("^([a-zA-Z]+)=(.*)$");
|
||||
private byte[] pubkeyBytes;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -48,12 +63,21 @@ public class CreateAddressActivity extends AppCompatActivity {
|
||||
String addressText = getAddress(uri);
|
||||
String[] parameters = getParameters(uri);
|
||||
for (String parameter : parameters) {
|
||||
String name = parameter.substring(0, 6).toLowerCase();
|
||||
if (name.startsWith("label")) {
|
||||
label.setText(parameter.substring(parameter.indexOf('=') + 1).trim());
|
||||
} else if (name.startsWith("action")) {
|
||||
parameter = parameter.toLowerCase();
|
||||
subscribe.setChecked(parameter.contains("subscribe"));
|
||||
Matcher matcher = KEY_VALUE_PATTERN.matcher(parameter);
|
||||
if (matcher.find()) {
|
||||
String key = matcher.group(1).toLowerCase();
|
||||
String value = matcher.group(2);
|
||||
switch (key) {
|
||||
case "label":
|
||||
label.setText(value.trim());
|
||||
break;
|
||||
case "action":
|
||||
subscribe.setChecked(value.trim().equalsIgnoreCase("subscribe"));
|
||||
break;
|
||||
case "pubkey":
|
||||
pubkeyBytes = Base64.decode(value, URL_SAFE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,6 +107,30 @@ public class CreateAddressActivity extends AppCompatActivity {
|
||||
if (subscribe.isChecked()) {
|
||||
bmc.addSubscribtion(bmAddress);
|
||||
}
|
||||
if (pubkeyBytes != null) {
|
||||
try {
|
||||
final Pubkey pubkey;
|
||||
InputStream pubkeyStream = new ByteArrayInputStream(pubkeyBytes);
|
||||
long stream = bmAddress.getStream();
|
||||
switch ((int) bmAddress.getVersion()) {
|
||||
case 2:
|
||||
pubkey = V2Pubkey.read(pubkeyStream, stream);
|
||||
break;
|
||||
case 3:
|
||||
pubkey = V3Pubkey.read(pubkeyStream, stream);
|
||||
break;
|
||||
case 4:
|
||||
pubkey = new V4Pubkey(V3Pubkey.read(pubkeyStream, stream));
|
||||
break;
|
||||
default:
|
||||
pubkey = null;
|
||||
}
|
||||
if (pubkey != null) {
|
||||
bmAddress.setPubkey(pubkey);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
setResult(Activity.RESULT_OK);
|
||||
finish();
|
||||
|
@ -16,16 +16,23 @@
|
||||
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Display;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -48,9 +55,6 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
|
||||
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
|
||||
import com.mikepenz.materialdrawer.model.interfaces.Nameable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
@ -64,6 +68,8 @@ import ch.dissem.apps.abit.repository.AndroidMessageRepository;
|
||||
import ch.dissem.apps.abit.service.BitmessageService;
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.apps.abit.synchronization.SyncAdapter;
|
||||
import ch.dissem.apps.abit.util.Drawables;
|
||||
import ch.dissem.apps.abit.util.Labels;
|
||||
import ch.dissem.apps.abit.util.Preferences;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
@ -72,6 +78,7 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static ch.dissem.apps.abit.ComposeMessageActivity.launchReplyTo;
|
||||
import static ch.dissem.apps.abit.repository.AndroidMessageRepository.LABEL_ARCHIVE;
|
||||
import static ch.dissem.apps.abit.service.BitmessageService.isRunning;
|
||||
|
||||
|
||||
@ -99,7 +106,6 @@ public class MainActivity extends AppCompatActivity
|
||||
public static final String EXTRA_REPLY_TO_MESSAGE = "ch.dissem.abit.ReplyToMessage";
|
||||
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
|
||||
private static final int ADD_IDENTITY = 1;
|
||||
private static final int MANAGE_IDENTITY = 2;
|
||||
|
||||
@ -233,6 +239,50 @@ public class MainActivity extends AppCompatActivity
|
||||
.withActivity(this)
|
||||
.withHeaderBackground(R.drawable.header)
|
||||
.withProfiles(profiles)
|
||||
.withOnAccountHeaderProfileImageListener(new AccountHeader.OnAccountHeaderProfileImageListener() {
|
||||
@Override
|
||||
public boolean onProfileImageClick(View view, IProfile profile, boolean current) {
|
||||
if (current) {
|
||||
// Show QR code in modal dialog
|
||||
final Dialog dialog = new Dialog(MainActivity.this);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
ImageView imageView = new ImageView(MainActivity.this);
|
||||
imageView.setImageBitmap(Drawables.qrCode(Singleton.getIdentity(MainActivity.this)));
|
||||
imageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
dialog.addContentView(imageView, new RelativeLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
Display display = window.getWindowManager().getDefaultDisplay();
|
||||
Point size = new Point();
|
||||
display.getSize(size);
|
||||
int dim = size.x < size.y ? size.x : size.y;
|
||||
|
||||
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
|
||||
lp.copyFrom(window.getAttributes());
|
||||
lp.width = dim;
|
||||
lp.height = dim;
|
||||
|
||||
window.setAttributes(lp);
|
||||
}
|
||||
dialog.show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onProfileImageLongClick(View view, IProfile iProfile, boolean b) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
|
||||
@Override
|
||||
public boolean onProfileChanged(View view, IProfile profile, boolean current) {
|
||||
@ -272,7 +322,7 @@ public class MainActivity extends AppCompatActivity
|
||||
final ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
|
||||
drawerItems.add(new PrimaryDrawerItem()
|
||||
.withName(R.string.archive)
|
||||
.withTag(AndroidMessageRepository.LABEL_ARCHIVE)
|
||||
.withTag(LABEL_ARCHIVE)
|
||||
.withIcon(CommunityMaterial.Icon.cmd_archive)
|
||||
);
|
||||
drawerItems.add(new DividerDrawerItem());
|
||||
@ -311,7 +361,15 @@ public class MainActivity extends AppCompatActivity
|
||||
public boolean onItemClick(View view, int position, IDrawerItem item) {
|
||||
if (item.getTag() instanceof Label) {
|
||||
selectedLabel = (Label) item.getTag();
|
||||
showSelectedLabel();
|
||||
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
|
||||
MessageListFragment) {
|
||||
((MessageListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
|
||||
} else {
|
||||
MessageListFragment listFragment = new MessageListFragment();
|
||||
changeList(listFragment);
|
||||
listFragment.updateList(selectedLabel);
|
||||
}
|
||||
return false;
|
||||
} else if (item instanceof Nameable<?>) {
|
||||
Nameable<?> ni = (Nameable<?>) item;
|
||||
@ -374,7 +432,10 @@ public class MainActivity extends AppCompatActivity
|
||||
for (Label label : labels) {
|
||||
addLabelEntry(label);
|
||||
}
|
||||
showSelectedLabel();
|
||||
IDrawerItem selectedDrawerItem = drawer.getDrawerItem(selectedLabel);
|
||||
if (selectedDrawerItem != null) {
|
||||
drawer.setSelection(selectedDrawerItem);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
@ -389,7 +450,11 @@ public class MainActivity extends AppCompatActivity
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
selectedLabel = (Label) savedInstanceState.getSerializable("selectedLabel");
|
||||
showSelectedLabel();
|
||||
|
||||
IDrawerItem selectedItem = drawer.getDrawerItem(selectedLabel);
|
||||
if (selectedItem != null) {
|
||||
drawer.setSelection(selectedItem);
|
||||
}
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
|
||||
@ -426,37 +491,9 @@ public class MainActivity extends AppCompatActivity
|
||||
public void addLabelEntry(Label label) {
|
||||
PrimaryDrawerItem item = new PrimaryDrawerItem()
|
||||
.withName(label.toString())
|
||||
.withTag(label);
|
||||
if (label.getType() == null) {
|
||||
item.withIcon(CommunityMaterial.Icon.cmd_label)
|
||||
.withIconColor(label.getColor());
|
||||
} else {
|
||||
switch (label.getType()) {
|
||||
case INBOX:
|
||||
item.withIcon(GoogleMaterial.Icon.gmd_inbox);
|
||||
break;
|
||||
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;
|
||||
case BROADCAST:
|
||||
item.withIcon(CommunityMaterial.Icon.cmd_rss);
|
||||
break;
|
||||
case UNREAD:
|
||||
item.withIcon(GoogleMaterial.Icon.gmd_markunread_mailbox);
|
||||
break;
|
||||
case TRASH:
|
||||
item.withIcon(GoogleMaterial.Icon.gmd_delete);
|
||||
break;
|
||||
default:
|
||||
item.withIcon(CommunityMaterial.Icon.cmd_label);
|
||||
}
|
||||
}
|
||||
.withTag(label)
|
||||
.withIcon(Labels.getIcon(label))
|
||||
.withIconColor(Labels.getColor(label));
|
||||
drawer.addItemAtPosition(item, drawer.getDrawerItems().size() - 3);
|
||||
}
|
||||
|
||||
@ -497,6 +534,7 @@ public class MainActivity extends AppCompatActivity
|
||||
for (IDrawerItem item : drawer.getDrawerItems()) {
|
||||
if (item.getTag() instanceof Label) {
|
||||
Label label = (Label) item.getTag();
|
||||
if (label != LABEL_ARCHIVE) {
|
||||
int unread = bmc.messages().countUnread(label);
|
||||
if (unread > 0) {
|
||||
((PrimaryDrawerItem) item).withBadge(String.valueOf(unread));
|
||||
@ -507,6 +545,7 @@ public class MainActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateNodeSwitch() {
|
||||
final MainActivity i = getInstance();
|
||||
@ -521,18 +560,6 @@ public class MainActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void showSelectedLabel() {
|
||||
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
|
||||
MessageListFragment) {
|
||||
((MessageListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
|
||||
} else {
|
||||
MessageListFragment listFragment = new MessageListFragment();
|
||||
changeList(listFragment);
|
||||
listFragment.updateList(selectedLabel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method from {@link ListSelectionListener}
|
||||
* indicating that the item with the given ID was selected.
|
||||
|
@ -16,8 +16,14 @@
|
||||
|
||||
package ch.dissem.apps.abit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@ -29,22 +35,29 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
||||
import com.mikepenz.iconics.view.IconicsImageView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
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.apps.abit.util.Labels;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
|
||||
import static android.text.util.Linkify.WEB_URLS;
|
||||
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN;
|
||||
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA;
|
||||
import static ch.dissem.apps.abit.util.Strings.normalizeWhitespaces;
|
||||
|
||||
|
||||
/**
|
||||
@ -106,6 +119,11 @@ public class MessageDetailFragment extends Fragment {
|
||||
} else if (item.getType() == Plaintext.Type.BROADCAST) {
|
||||
((TextView) rootView.findViewById(R.id.recipient)).setText(R.string.broadcast);
|
||||
}
|
||||
RecyclerView labelView = (RecyclerView) rootView.findViewById(R.id.labels);
|
||||
LabelAdapter labelAdapter = new LabelAdapter(getActivity(), item.getLabels());
|
||||
labelView.setAdapter(labelAdapter);
|
||||
labelView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
|
||||
|
||||
TextView messageBody = (TextView) rootView.findViewById(R.id.text);
|
||||
messageBody.setText(item.getText());
|
||||
|
||||
@ -130,16 +148,33 @@ public class MessageDetailFragment extends Fragment {
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
MessageRepository messageRepo = Singleton.getMessageRepository(inflater.getContext());
|
||||
if (removed) {
|
||||
if (getActivity() instanceof ActionBarListener) {
|
||||
((ActionBarListener) getActivity()).updateUnread();
|
||||
}
|
||||
Singleton.getMessageRepository(inflater.getContext()).save(item);
|
||||
messageRepo.save(item);
|
||||
}
|
||||
List<Plaintext> parents = new ArrayList<>(item.getParents().size());
|
||||
for (InventoryVector parentIV : item.getParents()) {
|
||||
Plaintext parent = messageRepo.getMessage(parentIV);
|
||||
if (parent != null) {
|
||||
parents.add(parent);
|
||||
}
|
||||
}
|
||||
showRelatedMessages(rootView, R.id.parents, parents);
|
||||
showRelatedMessages(rootView, R.id.responses, messageRepo.findResponses(item));
|
||||
}
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void showRelatedMessages(View rootView, @IdRes int id, List<Plaintext> messages) {
|
||||
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(id);
|
||||
RelatedMessageAdapter adapter = new RelatedMessageAdapter(getActivity(), messages);
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.message, menu);
|
||||
@ -197,4 +232,136 @@ public class MessageDetailFragment extends Fragment {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class RelatedMessageAdapter extends RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder> {
|
||||
private final List<Plaintext> messages;
|
||||
private final Context ctx;
|
||||
|
||||
private RelatedMessageAdapter(Context ctx, List<Plaintext> messages) {
|
||||
this.messages = messages;
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelatedMessageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
Context context = parent.getContext();
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
|
||||
// Inflate the custom layout
|
||||
View contactView = inflater.inflate(R.layout.item_message_minimized, parent, false);
|
||||
|
||||
// Return a new holder instance
|
||||
return new RelatedMessageAdapter.ViewHolder(contactView);
|
||||
}
|
||||
|
||||
// Involves populating data into the item through holder
|
||||
@Override
|
||||
public void onBindViewHolder(RelatedMessageAdapter.ViewHolder viewHolder, int position) {
|
||||
// Get the data model based on position
|
||||
Plaintext message = messages.get(position);
|
||||
|
||||
viewHolder.avatar.setImageDrawable(new Identicon(message.getFrom()));
|
||||
viewHolder.status.setImageResource(Assets.getStatusDrawable(message.getStatus()));
|
||||
viewHolder.sender.setText(message.getFrom().toString());
|
||||
viewHolder.extract.setText(normalizeWhitespaces(message.getText()));
|
||||
viewHolder.item = message;
|
||||
}
|
||||
|
||||
// Returns the total count of items in the list
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return messages.size();
|
||||
}
|
||||
|
||||
class ViewHolder extends RecyclerView.ViewHolder {
|
||||
private final ImageView avatar;
|
||||
private final ImageView status;
|
||||
private final TextView sender;
|
||||
private final TextView extract;
|
||||
private Plaintext item;
|
||||
|
||||
ViewHolder(final View itemView) {
|
||||
super(itemView);
|
||||
avatar = (ImageView) itemView.findViewById(R.id.avatar);
|
||||
status = (ImageView) itemView.findViewById(R.id.status);
|
||||
sender = (TextView) itemView.findViewById(R.id.sender);
|
||||
extract = (TextView) itemView.findViewById(R.id.text);
|
||||
itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (ctx instanceof MainActivity) {
|
||||
((MainActivity) ctx).onItemSelected(item);
|
||||
} else {
|
||||
Intent detailIntent;
|
||||
detailIntent = new Intent(ctx, MessageDetailActivity.class);
|
||||
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
|
||||
ctx.startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class LabelAdapter extends
|
||||
RecyclerView.Adapter<LabelAdapter.ViewHolder> {
|
||||
|
||||
private final List<Label> labels;
|
||||
private final Context ctx;
|
||||
|
||||
private LabelAdapter(Context ctx, Set<Label> labels) {
|
||||
this.labels = new ArrayList<>(labels);
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LabelAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
Context context = parent.getContext();
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
|
||||
// Inflate the custom layout
|
||||
View contactView = inflater.inflate(R.layout.item_label, parent, false);
|
||||
|
||||
// Return a new holder instance
|
||||
return new ViewHolder(contactView);
|
||||
}
|
||||
|
||||
// Involves populating data into the item through holder
|
||||
@Override
|
||||
public void onBindViewHolder(LabelAdapter.ViewHolder viewHolder, int position) {
|
||||
// Get the data model based on position
|
||||
Label label = labels.get(position);
|
||||
|
||||
viewHolder.icon.setColor(Labels.getColor(label));
|
||||
viewHolder.icon.setIcon(Labels.getIcon(label));
|
||||
viewHolder.label.setText(Labels.getText(label, ctx));
|
||||
}
|
||||
|
||||
// Returns the total count of items in the list
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return labels.size();
|
||||
}
|
||||
|
||||
// Provide a direct reference to each of the views within a data item
|
||||
// Used to cache the views within the item layout for fast access
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
// Your holder should contain a member variable
|
||||
// for any view that will be set as you render a row
|
||||
public IconicsImageView icon;
|
||||
public TextView label;
|
||||
|
||||
// We also create a constructor that accepts the entire item row
|
||||
// and does the view lookups to find each subview
|
||||
ViewHolder(View itemView) {
|
||||
// Stores the itemView in a public final member variable that can be used
|
||||
// to access the context from any ViewHolder instance.
|
||||
super(itemView);
|
||||
|
||||
icon = (IconicsImageView) itemView.findViewById(R.id.icon);
|
||||
label = (TextView) itemView.findViewById(R.id.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,10 @@ import android.widget.Toast;
|
||||
import com.mikepenz.aboutlibraries.Libs;
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder;
|
||||
|
||||
import ch.dissem.apps.abit.repository.AndroidNodeRegistry;
|
||||
import ch.dissem.apps.abit.service.Singleton;
|
||||
import ch.dissem.apps.abit.synchronization.SyncAdapter;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW;
|
||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
|
||||
@ -78,7 +80,9 @@ public class SettingsFragment
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
Singleton.getBitmessageContext(ctx).cleanup();
|
||||
BitmessageContext bmc = Singleton.getBitmessageContext(ctx);
|
||||
bmc.cleanup();
|
||||
bmc.internals().getNodeRegistry().clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,7 @@ public class SelectEncodingDialogFragment extends AppCompatDialogFragment {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
|
||||
savedInstanceState) {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
if (getArguments() != null && getArguments().containsKey(EXTRA_ENCODING)) {
|
||||
encoding = (Plaintext.Encoding) getArguments().getSerializable(EXTRA_ENCODING);
|
||||
}
|
||||
|
@ -31,8 +31,10 @@ import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import ch.dissem.apps.abit.R;
|
||||
import ch.dissem.apps.abit.util.Labels;
|
||||
import ch.dissem.apps.abit.util.UuidUtils;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
||||
@ -40,6 +42,8 @@ import ch.dissem.bitmessage.ports.AbstractMessageRepository;
|
||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import static ch.dissem.apps.abit.util.UuidUtils.asUuid;
|
||||
import static ch.dissem.bitmessage.utils.Strings.hex;
|
||||
import static java.lang.String.valueOf;
|
||||
|
||||
/**
|
||||
@ -65,6 +69,9 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
|
||||
private static final String COLUMN_RETRIES = "retries";
|
||||
private static final String COLUMN_NEXT_TRY = "next_try";
|
||||
private static final String COLUMN_INITIAL_HASH = "initial_hash";
|
||||
private static final String COLUMN_CONVERSATION = "conversation";
|
||||
|
||||
private static final String PARENTS_TABLE_NAME = "Message_Parent";
|
||||
|
||||
private static final String JOIN_TABLE_NAME = "Message_Label";
|
||||
private static final String JT_COLUMN_MESSAGE = "message_id";
|
||||
@ -122,35 +129,9 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
|
||||
private Label getLabel(Cursor c) {
|
||||
String typeName = c.getString(c.getColumnIndex(LBL_COLUMN_TYPE));
|
||||
Label.Type type = typeName == null ? null : Label.Type.valueOf(typeName);
|
||||
String text;
|
||||
if (type == null) {
|
||||
String text = Labels.getText(type, null, context);
|
||||
if (text == null) {
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
} else {
|
||||
switch (type) {
|
||||
case INBOX:
|
||||
text = context.getString(R.string.inbox);
|
||||
break;
|
||||
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;
|
||||
case UNREAD:
|
||||
text = context.getString(R.string.unread);
|
||||
break;
|
||||
case TRASH:
|
||||
text = context.getString(R.string.trash);
|
||||
break;
|
||||
case BROADCAST:
|
||||
text = context.getString(R.string.broadcasts);
|
||||
break;
|
||||
default:
|
||||
text = c.getString(c.getColumnIndex(LBL_COLUMN_LABEL));
|
||||
}
|
||||
}
|
||||
Label label = new Label(
|
||||
text,
|
||||
@ -164,7 +145,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
|
||||
public int countUnread(Label label) {
|
||||
String[] args;
|
||||
String where;
|
||||
if (label == null){
|
||||
if (label == null) {
|
||||
return 0;
|
||||
}
|
||||
if (label == LABEL_ARCHIVE) {
|
||||
@ -187,6 +168,72 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UUID> findConversations(Label label) {
|
||||
String[] projection = {
|
||||
COLUMN_CONVERSATION,
|
||||
};
|
||||
|
||||
String where;
|
||||
if (label == null) {
|
||||
where = "id NOT IN (SELECT message_id FROM Message_Label)";
|
||||
} else {
|
||||
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")";
|
||||
}
|
||||
List<UUID> result = new LinkedList<>();
|
||||
SQLiteDatabase db = sql.getReadableDatabase();
|
||||
try (Cursor c = db.query(
|
||||
TABLE_NAME, projection,
|
||||
where,
|
||||
null, null, null, null
|
||||
)) {
|
||||
while (c.moveToNext()) {
|
||||
byte[] uuidBytes = c.getBlob(c.getColumnIndex(COLUMN_CONVERSATION));
|
||||
result.add(asUuid(uuidBytes));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private void updateParents(SQLiteDatabase db, Plaintext message) {
|
||||
if (message.getInventoryVector() == null || message.getParents().isEmpty()) {
|
||||
// There are no parents to save yet (they are saved in the extended data, that's enough for now)
|
||||
return;
|
||||
}
|
||||
byte[] childIV = message.getInventoryVector().getHash();
|
||||
db.delete(PARENTS_TABLE_NAME, "child=?", new String[]{hex(childIV).toString()});
|
||||
|
||||
// save new parents
|
||||
int order = 0;
|
||||
for (InventoryVector parentIV : message.getParents()) {
|
||||
Plaintext parent = getMessage(parentIV);
|
||||
mergeConversations(db, parent.getConversationId(), message.getConversationId());
|
||||
order++;
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("parent", parentIV.getHash());
|
||||
values.put("child", childIV);
|
||||
values.put("pos", order);
|
||||
values.put("conversation", UuidUtils.asBytes(message.getConversationId()));
|
||||
db.insertOrThrow(PARENTS_TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces every occurrence of the source conversation ID with the target ID
|
||||
*
|
||||
* @param db is used to keep everything within one transaction
|
||||
* @param source ID of the conversation to be merged
|
||||
* @param target ID of the merge target
|
||||
*/
|
||||
private void mergeConversations(SQLiteDatabase db, UUID source, UUID target) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("conversation", UuidUtils.asBytes(target));
|
||||
String[] whereArgs = {hex(UuidUtils.asBytes(source)).toString()};
|
||||
db.update(TABLE_NAME, values, "conversation=?", whereArgs);
|
||||
db.update(PARENTS_TABLE_NAME, values, "conversation=?", whereArgs);
|
||||
}
|
||||
|
||||
protected List<Plaintext> find(String where) {
|
||||
List<Plaintext> 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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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(", ");
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
77
app/src/main/java/ch/dissem/apps/abit/util/Labels.java
Normal file
77
app/src/main/java/ch/dissem/apps/abit/util/Labels.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
37
app/src/main/java/ch/dissem/apps/abit/util/UuidUtils.java
Normal file
37
app/src/main/java/ch/dissem/apps/abit/util/UuidUtils.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
10
app/src/main/res/drawable/border_bottom.xml
Normal file
10
app/src/main/res/drawable/border_bottom.xml
Normal file
@ -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>
|
@ -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>
|
||||
|
@ -9,6 +9,7 @@
|
||||
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>
|
||||
|
28
app/src/main/res/layout/item_label.xml
Normal file
28
app/src/main/res/layout/item_label.xml
Normal file
@ -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>
|
62
app/src/main/res/layout/item_message_minimized.xml
Normal file
62
app/src/main/res/layout/item_message_minimized.xml
Normal file
@ -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>
|
@ -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,8 +29,7 @@
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
22
gradlew
vendored
22
gradlew
vendored
@ -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" "$@"
|
||||
|
6
gradlew.bat
vendored
6
gradlew.bat
vendored
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user