Merge branch 'feature/nio' into develop

This commit is contained in:
Christian Basler 2016-09-21 20:40:20 +02:00
commit d416db1307
24 changed files with 752 additions and 526 deletions

View File

@ -11,7 +11,7 @@ if (project.hasProperty("project.configs")
android { android {
compileSdkVersion 24 compileSdkVersion 24
buildToolsVersion "24.0.1" buildToolsVersion "24.0.2"
defaultConfig { defaultConfig {
applicationId "ch.dissem.apps." + appName.toLowerCase() applicationId "ch.dissem.apps." + appName.toLowerCase()
@ -29,12 +29,12 @@ android {
} }
} }
ext.jabitVersion = 'develop-SNAPSHOT' ext.jabitVersion = 'development-SNAPSHOT'
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:24.1.1' compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:support-v4:24.1.1' compile 'com.android.support:support-v4:24.2.0'
compile 'com.android.support:design:24.1.1' compile 'com.android.support:design:24.2.0'
compile "ch.dissem.jabit:jabit-core:$jabitVersion" compile "ch.dissem.jabit:jabit-core:$jabitVersion"
compile "ch.dissem.jabit:jabit-networking:$jabitVersion" compile "ch.dissem.jabit:jabit-networking:$jabitVersion"
@ -44,20 +44,23 @@ dependencies {
compile 'org.slf4j:slf4j-android:1.7.12' compile 'org.slf4j:slf4j-android:1.7.12'
compile('com.mikepenz:materialdrawer:3.1.0@aar') { compile 'com.mikepenz:materialize:1.0.0@aar'
compile('com.mikepenz:materialdrawer:5.6.0@aar') {
transitive = true transitive = true
} }
compile('com.mikepenz:aboutlibraries:5.3.4@aar') { compile('com.mikepenz:aboutlibraries:5.8.1@aar') {
transitive = true transitive = true
} }
compile 'com.mikepenz:iconics:1.6.2@aar' compile 'com.mikepenz:iconics:1.6.2@aar'
compile 'com.mikepenz:community-material-typeface:1.1.71@aar' compile 'com.mikepenz:community-material-typeface:1.5.54.2@aar'
compile 'com.journeyapps:zxing-android-embedded:3.1.0@aar' compile 'com.journeyapps:zxing-android-embedded:3.1.0@aar'
compile 'com.google.zxing:core:3.2.0' compile 'com.google.zxing:core:3.2.0'
compile 'io.github.yavski:fab-speed-dial:1.0.2' compile 'io.github.yavski:fab-speed-dial:1.0.2'
compile 'com.github.amlcurran.showcaseview:library:5.4.3'
compile 'com.github.amlcurran.showcaseview:library:5.4.0' compile ('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.9.3@aar'){
transitive=true
}
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19' testCompile 'org.mockito:mockito-core:1.10.19'
@ -72,4 +75,4 @@ android {
lintOptions { lintOptions {
abortOnError false abortOnError false
} }
} }

View File

@ -0,0 +1,9 @@
CREATE TABLE Node (
stream BIGINT NOT NULL,
address BINARY(32) NOT NULL,
port INT NOT NULL,
services BIGINT NOT NULL,
time BIGINT NOT NULL,
PRIMARY KEY (stream, address, port)
);
CREATE INDEX idx_time on Node(time);

View File

@ -16,12 +16,7 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
/** /**
@ -33,18 +28,11 @@ import android.view.MenuItem;
* This activity is mostly just a 'shell' activity containing nothing * This activity is mostly just a 'shell' activity containing nothing
* more than a {@link AddressDetailFragment}. * more than a {@link AddressDetailFragment}.
*/ */
public class AddressDetailActivity extends AppCompatActivity { public class AddressDetailActivity extends DetailActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.scrolling_toolbar_layout);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Show the Up button in the action bar.
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state // savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity // saved from previous configurations of this activity
@ -68,21 +56,4 @@ public class AddressDetailActivity extends AppCompatActivity {
.commit(); .commit();
} }
} }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
} }

View File

@ -27,6 +27,7 @@ public class ComposeMessageActivity extends AppCompatActivity {
public static final String EXTRA_IDENTITY = "ch.dissem.abit.Message.SENDER"; public static final String EXTRA_IDENTITY = "ch.dissem.abit.Message.SENDER";
public static final String EXTRA_RECIPIENT = "ch.dissem.abit.Message.RECIPIENT"; public static final String EXTRA_RECIPIENT = "ch.dissem.abit.Message.RECIPIENT";
public static final String EXTRA_SUBJECT = "ch.dissem.abit.Message.SUBJECT"; public static final String EXTRA_SUBJECT = "ch.dissem.abit.Message.SUBJECT";
public static final String EXTRA_CONTENT = "ch.dissem.abit.Message.CONTENT";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {

View File

@ -18,6 +18,7 @@ package ch.dissem.apps.abit;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.Selection;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -34,6 +35,7 @@ import ch.dissem.apps.abit.adapter.ContactAdapter;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_CONTENT;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_RECIPIENT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_RECIPIENT;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_SUBJECT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_SUBJECT;
@ -45,6 +47,7 @@ public class ComposeMessageFragment extends Fragment {
private BitmessageAddress identity; private BitmessageAddress identity;
private BitmessageAddress recipient; private BitmessageAddress recipient;
private String subject; private String subject;
private String content;
private AutoCompleteTextView recipientInput; private AutoCompleteTextView recipientInput;
private EditText subjectInput; private EditText subjectInput;
private EditText bodyInput; private EditText bodyInput;
@ -71,6 +74,9 @@ public class ComposeMessageFragment extends Fragment {
if (getArguments().containsKey(EXTRA_SUBJECT)) { if (getArguments().containsKey(EXTRA_SUBJECT)) {
subject = getArguments().getString(EXTRA_SUBJECT); subject = getArguments().getString(EXTRA_SUBJECT);
} }
if (getArguments().containsKey(EXTRA_CONTENT)) {
content = getArguments().getString(EXTRA_CONTENT);
}
} else { } else {
throw new RuntimeException("No identity set for ComposeMessageFragment"); throw new RuntimeException("No identity set for ComposeMessageFragment");
} }
@ -106,6 +112,16 @@ public class ComposeMessageFragment extends Fragment {
subjectInput = (EditText) rootView.findViewById(R.id.subject); subjectInput = (EditText) rootView.findViewById(R.id.subject);
subjectInput.setText(subject); subjectInput.setText(subject);
bodyInput = (EditText) rootView.findViewById(R.id.body); bodyInput = (EditText) rootView.findViewById(R.id.body);
bodyInput.setText(content);
if (recipient == null) {
recipientInput.requestFocus();
} else if (subject == null || subject.isEmpty()) {
subjectInput.requestFocus();
} else {
bodyInput.requestFocus();
bodyInput.setSelection(0);
}
return rootView; return rootView;
} }
@ -126,7 +142,7 @@ public class ComposeMessageFragment extends Fragment {
recipient = new BitmessageAddress(inputString); recipient = new BitmessageAddress(inputString);
} catch (Exception e) { } catch (Exception e) {
List<BitmessageAddress> contacts = Singleton.getAddressRepository List<BitmessageAddress> contacts = Singleton.getAddressRepository
(getContext()).getContacts(); (getContext()).getContacts();
for (BitmessageAddress contact : contacts) { for (BitmessageAddress contact : contacts) {
if (inputString.equalsIgnoreCase(contact.getAlias())) { if (inputString.equalsIgnoreCase(contact.getAlias())) {
recipient = contact; recipient = contact;
@ -137,8 +153,8 @@ public class ComposeMessageFragment extends Fragment {
} }
} }
Singleton.getBitmessageContext(getContext()).send(identity, recipient, Singleton.getBitmessageContext(getContext()).send(identity, recipient,
subjectInput.getText().toString(), subjectInput.getText().toString(),
bodyInput.getText().toString()); bodyInput.getText().toString());
getActivity().finish(); getActivity().finish();
return true; return true;
default: default:

View File

@ -0,0 +1,52 @@
package ch.dissem.apps.abit;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import com.mikepenz.materialize.MaterializeBuilder;
/**
* @author Christian Basler
*/
public class DetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.scrolling_toolbar_layout);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Show the Up button in the action bar.
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
new MaterializeBuilder()
.withActivity(this)
.withStatusBarColorRes(R.color.colorPrimaryDark)
.withTranslucentStatusBarProgrammatically(true)
.withStatusBarPadding(true)
.build();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -32,7 +32,6 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
@ -43,10 +42,11 @@ import com.github.amlcurran.showcaseview.targets.Target;
import com.mikepenz.community_material_typeface_library.CommunityMaterial; import com.mikepenz.community_material_typeface_library.CommunityMaterial;
import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable; import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.accountswitcher.AccountHeader; import com.mikepenz.materialdrawer.interfaces.OnCheckedChangeListener;
import com.mikepenz.materialdrawer.accountswitcher.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.model.DividerDrawerItem; import com.mikepenz.materialdrawer.model.DividerDrawerItem;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem; import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
@ -55,7 +55,6 @@ import com.mikepenz.materialdrawer.model.SwitchDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile; import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import com.mikepenz.materialdrawer.model.interfaces.Nameable; import com.mikepenz.materialdrawer.model.interfaces.Nameable;
import com.mikepenz.materialdrawer.model.interfaces.OnCheckedChangeListener;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -100,7 +99,7 @@ import static ch.dissem.apps.abit.service.BitmessageService.isRunning;
* </p> * </p>
*/ */
public class MainActivity extends AppCompatActivity public class MainActivity extends AppCompatActivity
implements ListSelectionListener<Serializable>, ActionBarListener { implements ListSelectionListener<Serializable>, ActionBarListener {
public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"; public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage";
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"; public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
@ -109,7 +108,7 @@ public class MainActivity extends AppCompatActivity
private static final int MANAGE_IDENTITY = 2; private static final int MANAGE_IDENTITY = 2;
private static final int ADD_CHAN = 3; private static final int ADD_CHAN = 3;
public static WeakReference<MainActivity> instance; private static WeakReference<MainActivity> instance;
/** /**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet * Whether or not the activity is in two-pane mode, i.e. running on a tablet
@ -157,7 +156,7 @@ public class MainActivity extends AppCompatActivity
MessageListFragment listFragment = new MessageListFragment(); MessageListFragment listFragment = new MessageListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment) getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment)
.commit(); .commit();
if (findViewById(R.id.message_detail_container) != null) { if (findViewById(R.id.message_detail_container) != null) {
// The detail container view will be present only in the // The detail container view will be present only in the
@ -187,42 +186,42 @@ public class MainActivity extends AppCompatActivity
} }
if (drawer.isDrawerOpen()) { if (drawer.isDrawerOpen()) {
RelativeLayout.LayoutParams lps = new RelativeLayout.LayoutParams(ViewGroup RelativeLayout.LayoutParams lps = new RelativeLayout.LayoutParams(ViewGroup
.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); .LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
lps.addRule(RelativeLayout.ALIGN_PARENT_LEFT); lps.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
int margin = ((Number) (getResources().getDisplayMetrics().density * 12)).intValue(); int margin = ((Number) (getResources().getDisplayMetrics().density * 12)).intValue();
lps.setMargins(margin, margin, margin, margin); lps.setMargins(margin, margin, margin, margin);
showcaseView = new ShowcaseView.Builder(this) showcaseView = new ShowcaseView.Builder(this)
.withMaterialShowcase() .withMaterialShowcase()
.setStyle(R.style.CustomShowcaseTheme) .setStyle(R.style.CustomShowcaseTheme)
.setContentTitle(R.string.full_node) .setContentTitle(R.string.full_node)
.setContentText(R.string.full_node_description) .setContentText(R.string.full_node_description)
.setTarget(new Target() { .setTarget(new Target() {
@Override @Override
public Point getPoint() { public Point getPoint() {
View view = drawer.getStickyFooter(); View view = drawer.getStickyFooter();
int[] location = new int[2]; int[] location = new int[2];
view.getLocationInWindow(location); view.getLocationInWindow(location);
int x = location[0] + 7 * view.getWidth() / 8; int x = location[0] + 7 * view.getWidth() / 8;
int y = location[1] + view.getHeight() / 2; int y = location[1] + view.getHeight() / 2;
return new Point(x, y); return new Point(x, y);
}
} }
) }
.replaceEndButton(R.layout.showcase_button) )
.hideOnTouchOutside() .replaceEndButton(R.layout.showcase_button)
.build(); .hideOnTouchOutside()
.build();
showcaseView.setButtonPosition(lps); showcaseView.setButtonPosition(lps);
} }
} }
private void changeList(AbstractItemListFragment<?> listFragment) { private void changeList(AbstractItemListFragment<?> listFragment) {
getSupportFragmentManager() getSupportFragmentManager()
.beginTransaction() .beginTransaction()
.replace(R.id.item_list, listFragment) .replace(R.id.item_list, listFragment)
.addToBackStack(null) .addToBackStack(null)
.commit(); .commit();
if (twoPane) { if (twoPane) {
// In two-pane mode, list items should be given the // In two-pane mode, list items should be given the
@ -236,90 +235,91 @@ public class MainActivity extends AppCompatActivity
for (BitmessageAddress identity : bmc.addresses().getIdentities()) { for (BitmessageAddress identity : bmc.addresses().getIdentities()) {
LOG.info("Adding identity " + identity.getAddress()); LOG.info("Adding identity " + identity.getAddress());
profiles.add(new ProfileDrawerItem() profiles.add(new ProfileDrawerItem()
.withIcon(new Identicon(identity)) .withIcon(new Identicon(identity))
.withName(identity.toString()) .withName(identity.toString())
.withNameShown(true) .withNameShown(true)
.withEmail(identity.getAddress()) .withEmail(identity.getAddress())
.withTag(identity) .withTag(identity)
); );
} }
if (profiles.isEmpty()) { if (profiles.isEmpty()) {
// Create an initial identity // Create an initial identity
BitmessageAddress identity = Singleton.getIdentity(this); BitmessageAddress identity = Singleton.getIdentity(this);
profiles.add(new ProfileDrawerItem() profiles.add(new ProfileDrawerItem()
.withIcon(new Identicon(identity)) .withIcon(new Identicon(identity))
.withName(identity.toString()) .withName(identity.toString())
.withEmail(identity.getAddress()) .withEmail(identity.getAddress())
.withTag(identity) .withTag(identity)
); );
} }
profiles.add(new ProfileSettingDrawerItem() profiles.add(new ProfileSettingDrawerItem()
.withName(getString(R.string.add_identity)) .withName(getString(R.string.add_identity))
.withDescription(getString(R.string.add_identity_summary)) .withDescription(getString(R.string.add_identity_summary))
.withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add) .withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add)
.actionBar() .actionBar()
.paddingDp(5) .paddingDp(5)
.colorRes(R.color.icons)) .colorRes(R.color.icons))
.withIdentifier(ADD_IDENTITY) .withIdentifier(ADD_IDENTITY)
); );
profiles.add(new ProfileSettingDrawerItem() profiles.add(new ProfileSettingDrawerItem()
.withName(getString(R.string.add_chan)) .withName(getString(R.string.add_chan))
.withDescription(getString(R.string.add_chan_summary)) .withDescription(getString(R.string.add_chan_summary))
.withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add) .withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add)
.actionBar() .actionBar()
.paddingDp(5) .paddingDp(5)
.colorRes(R.color.icons)) .colorRes(R.color.icons))
.withIdentifier(ADD_CHAN) .withIdentifier(ADD_CHAN)
); );
profiles.add(new ProfileSettingDrawerItem() profiles.add(new ProfileSettingDrawerItem()
.withName(getString(R.string.manage_identity)) .withName(getString(R.string.manage_identity))
.withIcon(GoogleMaterial.Icon.gmd_settings) .withIcon(GoogleMaterial.Icon.gmd_settings)
.withIdentifier(MANAGE_IDENTITY) .withIdentifier(MANAGE_IDENTITY)
); );
// Create the AccountHeader // Create the AccountHeader
accountHeader = new AccountHeaderBuilder() accountHeader = new AccountHeaderBuilder()
.withActivity(this) .withActivity(this)
.withHeaderBackground(R.drawable.header) .withHeaderBackground(R.drawable.header)
.withProfiles(profiles) .withProfiles(profiles)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() { .withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override @Override
public boolean onProfileChanged(View view, IProfile profile, boolean public boolean onProfileChanged(View view, IProfile profile, boolean
currentProfile) { currentProfile) {
switch (profile.getIdentifier()) { switch ((int) profile.getIdentifier()) {
case ADD_IDENTITY: case ADD_IDENTITY:
addIdentityDialog(); addIdentityDialog();
break; break;
case ADD_CHAN: case ADD_CHAN:
addChanDialog(); addChanDialog();
break; break;
case MANAGE_IDENTITY: case MANAGE_IDENTITY:
Intent show = new Intent(MainActivity.this, Intent show = new Intent(MainActivity.this,
AddressDetailActivity.class); AddressDetailActivity.class);
show.putExtra(AddressDetailFragment.ARG_ITEM, show.putExtra(AddressDetailFragment.ARG_ITEM,
Singleton.getIdentity(getApplicationContext())); Singleton.getIdentity(getApplicationContext()));
startActivity(show); startActivity(show);
break; break;
default: default:
if (profile instanceof ProfileDrawerItem) { if (profile instanceof ProfileDrawerItem) {
Object tag = ((ProfileDrawerItem) profile).getTag(); Object tag = ((ProfileDrawerItem) profile).getTag();
if (tag instanceof BitmessageAddress) { if (tag instanceof BitmessageAddress) {
Singleton.setIdentity((BitmessageAddress) tag); Singleton.setIdentity((BitmessageAddress) tag);
}
} }
} }
// false if it should close the drawer
return false;
} }
}) // false if it should close the drawer
.build(); return false;
}
})
.build();
if (profiles.size() > 2) { // There's always the add and manage identity items if (profiles.size() > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles.get(0), true); accountHeader.setActiveProfile(profiles.get(0), true);
} }
ArrayList<IDrawerItem> drawerItems = new ArrayList<>(); ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
for (Label label : labels) { for (Label label : labels) {
PrimaryDrawerItem item = new PrimaryDrawerItem().withName(label.toString()).withTag PrimaryDrawerItem item = new PrimaryDrawerItem()
(label); .withName(label.toString())
.withTag(label);
if (label.getType() == null) { if (label.getType() == null) {
item.withIcon(CommunityMaterial.Icon.cmd_label); item.withIcon(CommunityMaterial.Icon.cmd_label);
} else { } else {
@ -349,146 +349,144 @@ public class MainActivity extends AppCompatActivity
drawerItems.add(item); drawerItems.add(item);
} }
drawerItems.add(new PrimaryDrawerItem() drawerItems.add(new PrimaryDrawerItem()
.withName(R.string.archive) .withName(R.string.archive)
.withTag(null) .withTag(null)
.withIcon(CommunityMaterial.Icon.cmd_archive) .withIcon(CommunityMaterial.Icon.cmd_archive)
); );
drawerItems.add(new DividerDrawerItem()); drawerItems.add(new DividerDrawerItem());
drawerItems.add(new PrimaryDrawerItem() drawerItems.add(new PrimaryDrawerItem()
.withName(R.string.contacts_and_subscriptions) .withName(R.string.contacts_and_subscriptions)
.withIcon(GoogleMaterial.Icon.gmd_contacts)); .withIcon(GoogleMaterial.Icon.gmd_contacts));
drawerItems.add(new PrimaryDrawerItem() drawerItems.add(new PrimaryDrawerItem()
.withName(R.string.settings) .withName(R.string.settings)
.withIcon(GoogleMaterial.Icon.gmd_settings)); .withIcon(GoogleMaterial.Icon.gmd_settings));
drawer = new DrawerBuilder() drawer = new DrawerBuilder()
.withActivity(this) .withActivity(this)
.withToolbar(toolbar) .withToolbar(toolbar)
.withAccountHeader(accountHeader) .withAccountHeader(accountHeader)
.withDrawerItems(drawerItems) .withDrawerItems(drawerItems)
.addStickyDrawerItems( .addStickyDrawerItems(
new SwitchDrawerItem() new SwitchDrawerItem()
.withName(R.string.full_node) .withName(R.string.full_node)
.withIcon(CommunityMaterial.Icon.cmd_cloud_outline) .withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
.withChecked(isRunning()) .withChecked(isRunning())
.withOnCheckedChangeListener(new OnCheckedChangeListener() { .withOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(IDrawerItem drawerItem, public void onCheckedChanged(IDrawerItem drawerItem,
CompoundButton buttonView, CompoundButton buttonView,
boolean isChecked) { boolean isChecked) {
if (isChecked) { if (isChecked) {
checkAndStartNode(buttonView); checkAndStartNode(buttonView);
} else { } else {
service.shutdownNode(); service.shutdownNode();
}
}
})
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long
l, IDrawerItem item) {
if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag();
showSelectedLabel();
return false;
} else if (item instanceof Nameable<?>) {
Nameable<?> ni = (Nameable<?>) item;
switch (ni.getNameRes()) {
case R.string.contacts_and_subscriptions:
if (!(getSupportFragmentManager().findFragmentById(R.id
.item_list) instanceof AddressListFragment)) {
changeList(new AddressListFragment());
} else {
((AddressListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList();
}
break;
case R.string.settings:
startActivity(new Intent(MainActivity.this, SettingsActivity
.class));
break;
case R.string.archive:
selectedLabel = null;
showSelectedLabel();
break;
case R.string.full_node:
return true;
} }
} }
})
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(View view, int position, IDrawerItem item) {
if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag();
showSelectedLabel();
return false; return false;
} else if (item instanceof Nameable<?>) {
Nameable<?> ni = (Nameable<?>) item;
switch (ni.getName().getTextRes()) {
case R.string.contacts_and_subscriptions:
if (!(getSupportFragmentManager().findFragmentById(R.id
.item_list) instanceof AddressListFragment)) {
changeList(new AddressListFragment());
} else {
((AddressListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList();
}
break;
case R.string.settings:
startActivity(new Intent(MainActivity.this, SettingsActivity
.class));
break;
case R.string.archive:
selectedLabel = null;
showSelectedLabel();
break;
case R.string.full_node:
return true;
}
} }
}) return false;
.withShowDrawerOnFirstLaunch(true) }
.build(); })
.withShowDrawerOnFirstLaunch(true)
.build();
} }
private void addIdentityDialog() { private void addIdentityDialog() {
new AlertDialog.Builder(MainActivity.this) new AlertDialog.Builder(MainActivity.this)
.setMessage(R.string.add_identity_warning) .setMessage(R.string.add_identity_warning)
.setPositiveButton(android.R.string.yes, new .setPositiveButton(android.R.string.yes, new
DialogInterface.OnClickListener() { DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
Toast.makeText(MainActivity.this,
R.string.toast_long_running_operation,
Toast.LENGTH_SHORT).show();
new AsyncTask<Void, Void, BitmessageAddress>() {
@Override @Override
public void onClick(DialogInterface dialog, protected BitmessageAddress doInBackground(Void... args) {
int which) { return bmc.createIdentity(false, Pubkey.Feature.DOES_ACK);
Toast.makeText(MainActivity.this,
R.string.toast_long_running_operation,
Toast.LENGTH_SHORT).show();
new AsyncTask<Void, Void, BitmessageAddress>() {
@Override
protected BitmessageAddress doInBackground(Void... args) {
return bmc.createIdentity(false, Pubkey.Feature.DOES_ACK);
}
@Override
protected void onPostExecute(BitmessageAddress chan) {
Toast.makeText(MainActivity.this,
R.string.toast_identity_created,
Toast.LENGTH_SHORT).show();
addIdentityEntry(chan);
}
}.execute();
} }
})
.setNegativeButton(android.R.string.no, null) @Override
.show(); protected void onPostExecute(BitmessageAddress chan) {
Toast.makeText(MainActivity.this,
R.string.toast_identity_created,
Toast.LENGTH_SHORT).show();
addIdentityEntry(chan);
}
}.execute();
}
})
.setNegativeButton(android.R.string.no, null)
.show();
} }
private void addChanDialog() { private void addChanDialog() {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
final View dialogView = getLayoutInflater().inflate(R.layout.dialog_input_passphrase, null); final View dialogView = getLayoutInflater().inflate(R.layout.dialog_input_passphrase, null);
new AlertDialog.Builder(MainActivity.this) new AlertDialog.Builder(MainActivity.this)
.setMessage(R.string.add_chan) .setMessage(R.string.add_chan)
.setView(dialogView) .setView(dialogView)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
TextView passphrase = (TextView) dialogView.findViewById(R.id.passphrase); TextView passphrase = (TextView) dialogView.findViewById(R.id.passphrase);
Toast.makeText(MainActivity.this, R.string.toast_long_running_operation, Toast.makeText(MainActivity.this, R.string.toast_long_running_operation,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
new AsyncTask<String, Void, BitmessageAddress>() { new AsyncTask<String, Void, BitmessageAddress>() {
@Override @Override
protected BitmessageAddress doInBackground(String... args) { protected BitmessageAddress doInBackground(String... args) {
String pass = args[0]; String pass = args[0];
BitmessageAddress chan = bmc.createChan(pass); BitmessageAddress chan = bmc.createChan(pass);
chan.setAlias(pass); chan.setAlias(pass);
bmc.addresses().save(chan); bmc.addresses().save(chan);
return chan; return chan;
} }
@Override @Override
protected void onPostExecute(BitmessageAddress chan) { protected void onPostExecute(BitmessageAddress chan) {
Toast.makeText(MainActivity.this, Toast.makeText(MainActivity.this,
R.string.toast_chan_created, R.string.toast_chan_created,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
addIdentityEntry(chan); addIdentityEntry(chan);
} }
}.execute(passphrase.getText().toString()); }.execute(passphrase.getText().toString());
} }
}) })
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
} }
@Override @Override
@ -500,18 +498,18 @@ public class MainActivity extends AppCompatActivity
private void addIdentityEntry(BitmessageAddress identity) { private void addIdentityEntry(BitmessageAddress identity) {
IProfile newProfile = new IProfile newProfile = new
ProfileDrawerItem() ProfileDrawerItem()
.withName(identity.toString()) .withName(identity.toString())
.withEmail(identity.getAddress()) .withEmail(identity.getAddress())
.withTag(identity); .withTag(identity);
if (accountHeader.getProfiles() != null) { if (accountHeader.getProfiles() != null) {
// we know that there are 3 setting // we know that there are 3 setting
// elements. // elements.
// Set the new profile above them ;) // Set the new profile above them ;)
accountHeader.addProfile( accountHeader.addProfile(
newProfile, accountHeader newProfile, accountHeader
.getProfiles().size() .getProfiles().size()
- 3); - 3);
} else { } else {
accountHeader.addProfiles(newProfile); accountHeader.addProfiles(newProfile);
} }
@ -530,20 +528,20 @@ public class MainActivity extends AppCompatActivity
service.startupNode(); service.startupNode();
} else { } else {
new AlertDialog.Builder(MainActivity.this) new AlertDialog.Builder(MainActivity.this)
.setMessage(R.string.full_node_warning) .setMessage(R.string.full_node_warning)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
service.startupNode(); service.startupNode();
} }
}) })
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
buttonView.setChecked(false); buttonView.setChecked(false);
} }
}) })
.show(); .show();
} }
} }
@ -556,7 +554,7 @@ public class MainActivity extends AppCompatActivity
if (unread > 0) { if (unread > 0) {
((PrimaryDrawerItem) item).withBadge(String.valueOf(unread)); ((PrimaryDrawerItem) item).withBadge(String.valueOf(unread));
} else { } else {
((PrimaryDrawerItem) item).withBadge(null); ((PrimaryDrawerItem) item).withBadge((String) null);
} }
} }
} }
@ -564,9 +562,9 @@ public class MainActivity extends AppCompatActivity
private void showSelectedLabel() { private void showSelectedLabel() {
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
MessageListFragment) { MessageListFragment) {
((MessageListFragment) getSupportFragmentManager() ((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(selectedLabel); .findFragmentById(R.id.item_list)).updateList(selectedLabel);
} else { } else {
MessageListFragment listFragment = new MessageListFragment(); MessageListFragment listFragment = new MessageListFragment();
changeList(listFragment); changeList(listFragment);
@ -593,12 +591,12 @@ public class MainActivity extends AppCompatActivity
fragment = new AddressDetailFragment(); fragment = new AddressDetailFragment();
else else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was " "was "
+ item.getClass().getSimpleName()); + item.getClass().getSimpleName());
fragment.setArguments(arguments); fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.message_detail_container, fragment) .replace(R.id.message_detail_container, fragment)
.commit(); .commit();
} else { } else {
// In single-pane mode, simply start the detail activity // In single-pane mode, simply start the detail activity
// for the selected item ID. // for the selected item ID.
@ -609,8 +607,8 @@ public class MainActivity extends AppCompatActivity
detailIntent = new Intent(this, AddressDetailActivity.class); detailIntent = new Intent(this, AddressDetailActivity.class);
else else
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " + throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was " "was "
+ item.getClass().getSimpleName()); + item.getClass().getSimpleName());
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item); detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
startActivity(detailIntent); startActivity(detailIntent);
@ -632,7 +630,7 @@ public class MainActivity extends AppCompatActivity
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
bindService(new Intent(this, BitmessageService.class), connection, Context bindService(new Intent(this, BitmessageService.class), connection, Context
.BIND_AUTO_CREATE); .BIND_AUTO_CREATE);
} }
@Override @Override

View File

@ -1,11 +1,6 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
/** /**
@ -17,18 +12,11 @@ import android.view.MenuItem;
* This activity is mostly just a 'shell' activity containing nothing * This activity is mostly just a 'shell' activity containing nothing
* more than a {@link MessageDetailFragment}. * more than a {@link MessageDetailFragment}.
*/ */
public class MessageDetailActivity extends AppCompatActivity { public class MessageDetailActivity extends DetailActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.scrolling_toolbar_layout);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Show the Up button in the action bar.
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state // savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity // saved from previous configurations of this activity
@ -52,21 +40,4 @@ public class MessageDetailActivity extends AppCompatActivity {
.commit(); .commit();
} }
} }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
} }

View File

@ -44,6 +44,7 @@ import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import static android.text.util.Linkify.WEB_URLS; import static android.text.util.Linkify.WEB_URLS;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_CONTENT;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_RECIPIENT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_RECIPIENT;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_SUBJECT; import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_SUBJECT;
@ -100,7 +101,7 @@ public class MessageDetailFragment extends Fragment {
((TextView) rootView.findViewById(R.id.subject)).setText(item.getSubject()); ((TextView) rootView.findViewById(R.id.subject)).setText(item.getSubject());
BitmessageAddress sender = item.getFrom(); BitmessageAddress sender = item.getFrom();
((ImageView) rootView.findViewById(R.id.avatar)).setImageDrawable(new Identicon ((ImageView) rootView.findViewById(R.id.avatar)).setImageDrawable(new Identicon
(sender)); (sender));
((TextView) rootView.findViewById(R.id.sender)).setText(sender.toString()); ((TextView) rootView.findViewById(R.id.sender)).setText(sender.toString());
if (item.getTo() != null) { if (item.getTo() != null) {
((TextView) rootView.findViewById(R.id.recipient)).setText(item.getTo().toString()); ((TextView) rootView.findViewById(R.id.recipient)).setText(item.getTo().toString());
@ -112,11 +113,11 @@ public class MessageDetailFragment extends Fragment {
Linkify.addLinks(messageBody, WEB_URLS); Linkify.addLinks(messageBody, WEB_URLS);
Linkify.addLinks(messageBody, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null, Linkify.addLinks(messageBody, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null,
new TransformFilter() { new TransformFilter() {
public final String transformUrl(final Matcher match, String url) { public final String transformUrl(final Matcher match, String url) {
return match.group(); return match.group();
} }
}); });
messageBody.setLinksClickable(true); messageBody.setLinksClickable(true);
messageBody.setTextIsSelectable(true); messageBody.setTextIsSelectable(true);
@ -146,7 +147,7 @@ public class MessageDetailFragment extends Fragment {
Drawables.addIcon(getActivity(), menu, R.id.reply, GoogleMaterial.Icon.gmd_reply); Drawables.addIcon(getActivity(), menu, R.id.reply, GoogleMaterial.Icon.gmd_reply);
Drawables.addIcon(getActivity(), menu, R.id.delete, GoogleMaterial.Icon.gmd_delete); Drawables.addIcon(getActivity(), menu, R.id.delete, GoogleMaterial.Icon.gmd_delete);
Drawables.addIcon(getActivity(), menu, R.id.mark_unread, GoogleMaterial.Icon Drawables.addIcon(getActivity(), menu, R.id.mark_unread, GoogleMaterial.Icon
.gmd_markunread); .gmd_markunread);
Drawables.addIcon(getActivity(), menu, R.id.archive, GoogleMaterial.Icon.gmd_archive); Drawables.addIcon(getActivity(), menu, R.id.archive, GoogleMaterial.Icon.gmd_archive);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
@ -158,17 +159,20 @@ public class MessageDetailFragment extends Fragment {
switch (menuItem.getItemId()) { switch (menuItem.getItemId()) {
case R.id.reply: case R.id.reply:
Intent replyIntent = new Intent(getActivity().getApplicationContext(), Intent replyIntent = new Intent(getActivity().getApplicationContext(),
ComposeMessageActivity.class); ComposeMessageActivity.class);
replyIntent.putExtra(EXTRA_RECIPIENT, item.getFrom()); replyIntent.putExtra(EXTRA_RECIPIENT, item.getFrom());
replyIntent.putExtra(EXTRA_IDENTITY, item.getTo()); replyIntent.putExtra(EXTRA_IDENTITY, item.getTo());
String prefix; String prefix;
if (item.getSubject().length() >= 3 && item.getSubject().substring(0, 3) if (item.getSubject().length() >= 3 && item.getSubject().substring(0, 3)
.equalsIgnoreCase("RE:")) { .equalsIgnoreCase("RE:")) {
prefix = ""; prefix = "";
} else { } else {
prefix = "RE: "; prefix = "RE: ";
} }
replyIntent.putExtra(EXTRA_SUBJECT, prefix + item.getSubject()); replyIntent.putExtra(EXTRA_SUBJECT, prefix + item.getSubject());
replyIntent.putExtra(EXTRA_CONTENT,
"\n\n------------------------------------------------------\n"
+ item.getText());
startActivity(replyIntent); startActivity(replyIntent);
return true; return true;
case R.id.delete: case R.id.delete:

View File

@ -7,22 +7,14 @@ import android.support.v7.widget.Toolbar;
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
public class SettingsActivity extends AppCompatActivity { public class SettingsActivity extends DetailActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.scrolling_toolbar_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(false);
// Display the fragment as the main content. // Display the fragment as the main content.
getFragmentManager().beginTransaction() getFragmentManager().beginTransaction()
.replace(R.id.content, new SettingsFragment()) .replace(R.id.content, new SettingsFragment())
.commit(); .commit();
} }
} }

View File

@ -21,6 +21,8 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.widget.TextView; import android.widget.TextView;
import com.mikepenz.materialize.MaterializeBuilder;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
@ -39,6 +41,13 @@ public class StatusActivity extends AppCompatActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(false); getSupportActionBar().setHomeButtonEnabled(false);
new MaterializeBuilder()
.withActivity(this)
.withStatusBarColorRes(R.color.colorPrimaryDark)
.withTranslucentStatusBarProgrammatically(true)
.withStatusBarPadding(true)
.build();
BitmessageContext bmc = Singleton.getBitmessageContext(this); BitmessageContext bmc = Singleton.getBitmessageContext(this);
StringBuilder status = new StringBuilder(); StringBuilder status = new StringBuilder();
for (BitmessageAddress address : bmc.addresses().getIdentities()) { for (BitmessageAddress address : bmc.addresses().getIdentities()) {

View File

@ -32,20 +32,29 @@ public class WifiReceiver extends BroadcastReceiver {
if (Preferences.isWifiOnly(ctx)) { if (Preferences.isWifiOnly(ctx)) {
BitmessageContext bmc = Singleton.getBitmessageContext(ctx); BitmessageContext bmc = Singleton.getBitmessageContext(ctx);
if (!isConnectedToWifi(ctx) && bmc.isRunning()) { if (isConnectedToMeteredNetwork(ctx) && bmc.isRunning()) {
bmc.shutdown(); bmc.shutdown();
} }
} }
} }
public static boolean isConnectedToWifi(Context ctx) { public static boolean isConnectedToMeteredNetwork(Context ctx) {
NetworkInfo netInfo = getNetworkInfo(ctx); NetworkInfo netInfo = getNetworkInfo(ctx);
return netInfo != null && netInfo.getType() == ConnectivityManager.TYPE_WIFI; if (netInfo == null || !netInfo.isConnectedOrConnecting()) {
return false;
}
switch (netInfo.getType()){
case ConnectivityManager.TYPE_ETHERNET:
case ConnectivityManager.TYPE_WIFI:
return false;
default:
return true;
}
} }
private static NetworkInfo getNetworkInfo(Context ctx) { private static NetworkInfo getNetworkInfo(Context ctx) {
ConnectivityManager conMan = (ConnectivityManager) ctx.getSystemService(Context ConnectivityManager conMan = (ConnectivityManager) ctx.getSystemService(Context
.CONNECTIVITY_SERVICE); .CONNECTIVITY_SERVICE);
return conMan.getActiveNetworkInfo(); return conMan.getActiveNetworkInfo();
} }
} }

View File

@ -119,37 +119,36 @@ public class AndroidAddressRepository implements AddressRepository {
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
// you will actually use after this query. // you will actually use after this query.
String[] projection = { String[] projection = {
COLUMN_ADDRESS, COLUMN_ADDRESS,
COLUMN_ALIAS, COLUMN_ALIAS,
COLUMN_PUBLIC_KEY, COLUMN_PUBLIC_KEY,
COLUMN_PRIVATE_KEY, COLUMN_PRIVATE_KEY,
COLUMN_SUBSCRIBED, COLUMN_SUBSCRIBED,
COLUMN_CHAN COLUMN_CHAN
}; };
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where, where,
null, null, null, null null, null, null, null
)) { )) {
c.moveToFirst(); while (c.moveToNext()) {
while (!c.isAfterLast()) {
BitmessageAddress address; BitmessageAddress address;
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY)); byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
if (privateKeyBytes != null) { if (privateKeyBytes != null) {
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
(privateKeyBytes)); (privateKeyBytes));
address = new BitmessageAddress(privateKey); address = new BitmessageAddress(privateKey);
} else { } else {
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS))); address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY)); byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
if (publicKeyBytes != null) { if (publicKeyBytes != null) {
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
.getStream(), .getStream(),
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length, new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
false); false);
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
pubkey = new V4Pubkey((V3Pubkey) pubkey); pubkey = new V4Pubkey((V3Pubkey) pubkey);
} }
@ -161,7 +160,6 @@ public class AndroidAddressRepository implements AddressRepository {
address.setSubscribed(c.getInt(c.getColumnIndex(COLUMN_SUBSCRIBED)) == 1); address.setSubscribed(c.getInt(c.getColumnIndex(COLUMN_SUBSCRIBED)) == 1);
result.add(address); result.add(address);
c.moveToNext();
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
@ -184,8 +182,10 @@ public class AndroidAddressRepository implements AddressRepository {
private boolean exists(BitmessageAddress address) { private boolean exists(BitmessageAddress address) {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM Address WHERE address='" + address try (Cursor cursor = db.rawQuery(
.getAddress() + "'", null)) { "SELECT COUNT(*) FROM Address WHERE address=?",
new String[]{address.getAddress()}
)) {
cursor.moveToFirst(); cursor.moveToFirst();
return cursor.getInt(0) > 0; return cursor.getInt(0) > 0;
} }
@ -210,8 +210,8 @@ public class AndroidAddressRepository implements AddressRepository {
values.put(COLUMN_CHAN, address.isChan()); values.put(COLUMN_CHAN, address.isChan());
values.put(COLUMN_SUBSCRIBED, address.isSubscribed()); values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
int update = db.update(TABLE_NAME, values, "address = '" + address.getAddress() + int update = db.update(TABLE_NAME, values, "address=?",
"'", null); new String[]{address.getAddress()});
if (update < 0) { if (update < 0) {
LOG.error("Could not update address " + address); LOG.error("Could not update address " + address);
} }

View File

@ -25,7 +25,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -42,6 +41,7 @@ import ch.dissem.bitmessage.utils.Encode;
import static ch.dissem.apps.abit.repository.SqlHelper.join; import static ch.dissem.apps.abit.repository.SqlHelper.join;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static ch.dissem.bitmessage.utils.UnixTime.now; import static ch.dissem.bitmessage.utils.UnixTime.now;
import static java.lang.String.valueOf;
/** /**
* {@link Inventory} implementation using the Android SQL API. * {@link Inventory} implementation using the Android SQL API.
@ -88,21 +88,19 @@ public class AndroidInventory implements Inventory {
cache.put(stream, result); cache.put(stream, result);
String[] projection = { String[] projection = {
COLUMN_HASH, COLUMN_EXPIRES COLUMN_HASH, COLUMN_EXPIRES
}; };
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
"stream = " + stream, "stream = " + stream,
null, null, null, null null, null, null, null
)) { )) {
c.moveToFirst(); while (c.moveToNext()) {
while (!c.isAfterLast()) {
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
long expires = c.getLong(c.getColumnIndex(COLUMN_EXPIRES)); long expires = c.getLong(c.getColumnIndex(COLUMN_EXPIRES));
result.put(new InventoryVector(blob), expires); result.put(new InventoryVector(blob), expires);
c.moveToNext();
} }
} }
LOG.info("Stream #" + stream + " inventory size: " + result.size()); LOG.info("Stream #" + stream + " inventory size: " + result.size());
@ -126,18 +124,17 @@ public class AndroidInventory implements Inventory {
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
// you will actually use after this query. // you will actually use after this query.
String[] projection = { String[] projection = {
COLUMN_VERSION, COLUMN_VERSION,
COLUMN_DATA COLUMN_DATA
}; };
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
"hash = X'" + vector + "'", "hash = X'" + vector + "'",
null, null, null, null null, null, null, null
)) { )) {
c.moveToFirst(); if (!c.moveToFirst()) {
if (c.isAfterLast()) {
LOG.info("Object requested that we don't have. IV: " + vector); LOG.info("Object requested that we don't have. IV: " + vector);
return null; return null;
} }
@ -153,8 +150,8 @@ public class AndroidInventory implements Inventory {
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
// you will actually use after this query. // you will actually use after this query.
String[] projection = { String[] projection = {
COLUMN_VERSION, COLUMN_VERSION,
COLUMN_DATA COLUMN_DATA
}; };
StringBuilder where = new StringBuilder("1=1"); StringBuilder where = new StringBuilder("1=1");
if (stream > 0) { if (stream > 0) {
@ -170,17 +167,15 @@ public class AndroidInventory implements Inventory {
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
List<ObjectMessage> result = new LinkedList<>(); List<ObjectMessage> result = new LinkedList<>();
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where.toString(), where.toString(),
null, null, null, null null, null, null, null
)) { )) {
c.moveToFirst(); while (c.moveToNext()) {
while (!c.isAfterLast()) {
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION)); int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob), result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
blob.length)); blob.length));
c.moveToNext();
} }
} }
return result; return result;
@ -211,8 +206,6 @@ public class AndroidInventory implements Inventory {
getCache(object.getStream()).put(iv, object.getExpiresTime()); getCache(object.getStream()).put(iv, object.getExpiresTime());
} catch (SQLiteConstraintException e) { } catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e); LOG.trace(e.getMessage(), e);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} }
} }
@ -225,7 +218,7 @@ public class AndroidInventory implements Inventory {
public void cleanup() { public void cleanup() {
long fiveMinutesAgo = now() - 5 * MINUTE; long fiveMinutesAgo = now() - 5 * MINUTE;
SQLiteDatabase db = sql.getWritableDatabase(); SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME, "expires < " + fiveMinutesAgo, null); db.delete(TABLE_NAME, "expires < ?", new String[]{valueOf(fiveMinutesAgo)});
for (Map<InventoryVector, Long> c : cache.values()) { for (Map<InventoryVector, Long> c : cache.values()) {
Iterator<Map.Entry<InventoryVector, Long>> iterator = c.entrySet().iterator(); Iterator<Map.Entry<InventoryVector, Long>> iterator = c.entrySet().iterator();

View File

@ -41,6 +41,8 @@ import ch.dissem.bitmessage.ports.AbstractMessageRepository;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.utils.Encode; import ch.dissem.bitmessage.utils.Encode;
import static java.lang.String.valueOf;
/** /**
* {@link MessageRepository} implementation using the Android SQL API. * {@link MessageRepository} implementation using the Android SQL API.
*/ */
@ -87,23 +89,21 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
// you will actually use after this query. // you will actually use after this query.
String[] projection = { String[] projection = {
LBL_COLUMN_ID, LBL_COLUMN_ID,
LBL_COLUMN_LABEL, LBL_COLUMN_LABEL,
LBL_COLUMN_TYPE, LBL_COLUMN_TYPE,
LBL_COLUMN_COLOR LBL_COLUMN_COLOR
}; };
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query( try (Cursor c = db.query(
LBL_TABLE_NAME, projection, LBL_TABLE_NAME, projection,
where, where,
null, null, null, null, null, null,
LBL_COLUMN_ORDER LBL_COLUMN_ORDER
)) { )) {
c.moveToFirst(); while (c.moveToNext()) {
while (!c.isAfterLast()) {
result.add(getLabel(c)); result.add(getLabel(c));
c.moveToNext();
} }
} }
return result; return result;
@ -140,26 +140,34 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} }
} }
Label label = new Label( Label label = new Label(
text, text,
type, type,
c.getInt(c.getColumnIndex(LBL_COLUMN_COLOR))); c.getInt(c.getColumnIndex(LBL_COLUMN_COLOR)));
label.setId(c.getLong(c.getColumnIndex(LBL_COLUMN_ID))); label.setId(c.getLong(c.getColumnIndex(LBL_COLUMN_ID)));
return label; return label;
} }
@Override @Override
public int countUnread(Label label) { public int countUnread(Label label) {
String[] args;
String where; String where;
if (label != null) { if (label != null) {
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=?) AND ";
+ ") AND "; args = new String[]{
label.getId().toString(),
Label.Type.UNREAD.name()
};
} else { } else {
where = ""; where = "";
args = new String[]{
Label.Type.UNREAD.name()
};
} }
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
return (int) DatabaseUtils.queryNumEntries(db, TABLE_NAME, return (int) DatabaseUtils.queryNumEntries(db, TABLE_NAME,
where + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + where + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" +
"SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))" "SELECT id FROM Label WHERE type=?))",
args
); );
} }
@ -169,48 +177,47 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
// you will actually use after this query. // you will actually use after this query.
String[] projection = { String[] projection = {
COLUMN_ID, COLUMN_ID,
COLUMN_IV, COLUMN_IV,
COLUMN_TYPE, COLUMN_TYPE,
COLUMN_SENDER, COLUMN_SENDER,
COLUMN_RECIPIENT, COLUMN_RECIPIENT,
COLUMN_DATA, COLUMN_DATA,
COLUMN_ACK_DATA, COLUMN_ACK_DATA,
COLUMN_SENT, COLUMN_SENT,
COLUMN_RECEIVED, COLUMN_RECEIVED,
COLUMN_STATUS, COLUMN_STATUS,
COLUMN_TTL, COLUMN_TTL,
COLUMN_RETRIES, COLUMN_RETRIES,
COLUMN_NEXT_TRY COLUMN_NEXT_TRY
}; };
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where, where,
null, null, null, null, null, null,
COLUMN_RECEIVED + " DESC" COLUMN_RECEIVED + " DESC"
)) { )) {
c.moveToFirst(); while (c.moveToNext()) {
while (!c.isAfterLast()) {
byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV)); byte[] iv = c.getBlob(c.getColumnIndex(COLUMN_IV));
byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] data = c.getBlob(c.getColumnIndex(COLUMN_DATA));
Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex Plaintext.Type type = Plaintext.Type.valueOf(c.getString(c.getColumnIndex
(COLUMN_TYPE))); (COLUMN_TYPE)));
Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new Plaintext.Builder builder = Plaintext.readWithoutSignature(type, new
ByteArrayInputStream(data)); ByteArrayInputStream(data));
long id = c.getLong(c.getColumnIndex(COLUMN_ID)); long id = c.getLong(c.getColumnIndex(COLUMN_ID));
builder.id(id); builder.id(id);
builder.IV(new InventoryVector(iv)); builder.IV(new InventoryVector(iv));
builder.from(ctx.getAddressRepository().getAddress(c.getString(c.getColumnIndex builder.from(ctx.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_SENDER)))); (COLUMN_SENDER))));
builder.to(ctx.getAddressRepository().getAddress(c.getString(c.getColumnIndex builder.to(ctx.getAddressRepository().getAddress(c.getString(c.getColumnIndex
(COLUMN_RECIPIENT)))); (COLUMN_RECIPIENT))));
builder.ackData(c.getBlob(c.getColumnIndex(COLUMN_ACK_DATA))); builder.ackData(c.getBlob(c.getColumnIndex(COLUMN_ACK_DATA)));
builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT))); builder.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED))); builder.received(c.getLong(c.getColumnIndex(COLUMN_RECEIVED)));
builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex builder.status(Plaintext.Status.valueOf(c.getString(c.getColumnIndex
(COLUMN_STATUS)))); (COLUMN_STATUS))));
builder.ttl(c.getLong(c.getColumnIndex(COLUMN_TTL))); builder.ttl(c.getLong(c.getColumnIndex(COLUMN_TTL)));
builder.retries(c.getInt(c.getColumnIndex(COLUMN_RETRIES))); builder.retries(c.getInt(c.getColumnIndex(COLUMN_RETRIES)));
int nextTryColumn = c.getColumnIndex(COLUMN_NEXT_TRY); int nextTryColumn = c.getColumnIndex(COLUMN_NEXT_TRY);
@ -219,7 +226,6 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} }
builder.labels(findLabels(id)); builder.labels(findLabels(id));
result.add(builder.build()); result.add(builder.build());
c.moveToNext();
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
@ -240,7 +246,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
// save from address if necessary // save from address if necessary
if (message.getId() == null) { if (message.getId() == null) {
BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(message BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(message
.getFrom().getAddress()); .getFrom().getAddress());
if (savedAddress == null || savedAddress.getPrivateKey() == null) { if (savedAddress == null || savedAddress.getPrivateKey() == null) {
if (savedAddress != null && savedAddress.getAlias() != null) { if (savedAddress != null && savedAddress.getAlias() != null) {
message.getFrom().setAlias(savedAddress.getAlias()); message.getFrom().setAlias(savedAddress.getAlias());
@ -257,7 +263,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} }
// remove existing labels // remove existing labels
db.delete(JOIN_TABLE_NAME, "message_id=" + message.getId(), null); db.delete(JOIN_TABLE_NAME, "message_id=?", new String[]{valueOf(message.getId())});
// save labels // save labels
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
@ -279,15 +285,19 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
private void insert(SQLiteDatabase db, Plaintext message) throws IOException { private void insert(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash()); .getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name()); values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress()); values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
values.put(COLUMN_DATA, Encode.bytes(message)); values.put(COLUMN_DATA, Encode.bytes(message));
values.put(COLUMN_ACK_DATA, message.getAckData());
values.put(COLUMN_SENT, message.getSent()); values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived()); values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name()); values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash()); values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
values.put(COLUMN_TTL, message.getTTL());
values.put(COLUMN_RETRIES, message.getRetries());
values.put(COLUMN_NEXT_TRY, message.getNextTry());
long id = db.insertOrThrow(TABLE_NAME, null, values); long id = db.insertOrThrow(TABLE_NAME, null, values);
message.setId(id); message.setId(id);
} }
@ -295,15 +305,19 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
private void update(SQLiteDatabase db, Plaintext message) throws IOException { private void update(SQLiteDatabase db, Plaintext message) throws IOException {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message values.put(COLUMN_IV, message.getInventoryVector() == null ? null : message
.getInventoryVector().getHash()); .getInventoryVector().getHash());
values.put(COLUMN_TYPE, message.getType().name()); values.put(COLUMN_TYPE, message.getType().name());
values.put(COLUMN_SENDER, message.getFrom().getAddress()); values.put(COLUMN_SENDER, message.getFrom().getAddress());
values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress()); values.put(COLUMN_RECIPIENT, message.getTo() == null ? null : message.getTo().getAddress());
values.put(COLUMN_DATA, Encode.bytes(message)); values.put(COLUMN_DATA, Encode.bytes(message));
values.put(COLUMN_ACK_DATA, message.getAckData());
values.put(COLUMN_SENT, message.getSent()); values.put(COLUMN_SENT, message.getSent());
values.put(COLUMN_RECEIVED, message.getReceived()); values.put(COLUMN_RECEIVED, message.getReceived());
values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name()); values.put(COLUMN_STATUS, message.getStatus() == null ? null : message.getStatus().name());
values.put(COLUMN_INITIAL_HASH, message.getInitialHash()); values.put(COLUMN_INITIAL_HASH, message.getInitialHash());
values.put(COLUMN_TTL, message.getTTL());
values.put(COLUMN_RETRIES, message.getRetries());
values.put(COLUMN_NEXT_TRY, message.getNextTry());
db.update(TABLE_NAME, values, "id = " + message.getId(), null); db.update(TABLE_NAME, values, "id = " + message.getId(), null);
} }

View File

@ -0,0 +1,187 @@
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteStatement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.ports.NodeRegistry;
import ch.dissem.bitmessage.utils.Collections;
import ch.dissem.bitmessage.utils.SqlStrings;
import static ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes;
import static ch.dissem.bitmessage.utils.Strings.hex;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
import static ch.dissem.bitmessage.utils.UnixTime.now;
import static java.lang.String.valueOf;
/**
* @author Christian Basler
*/
public class AndroidNodeRegistry implements NodeRegistry {
private static final Logger LOG = LoggerFactory.getLogger(AndroidInventory.class);
private static final String TABLE_NAME = "Node";
private static final String COLUMN_STREAM = "stream";
private static final String COLUMN_ADDRESS = "address";
private static final String COLUMN_PORT = "port";
private static final String COLUMN_SERVICES = "services";
private static final String COLUMN_TIME = "time";
private final ThreadLocal<SQLiteStatement> loadExistingStatement = new ThreadLocal<>();
private final SqlHelper sql;
private Map<Long, Set<NetworkAddress>> stableNodes;
public AndroidNodeRegistry(SqlHelper sql) {
this.sql = sql;
cleanUp();
}
private void cleanUp() {
SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME, "time < ?", new String[]{valueOf(now(-28 * DAY))});
}
private Long loadExistingTime(NetworkAddress node) {
SQLiteStatement statement = loadExistingStatement.get();
if (statement == null) {
statement = sql.getWritableDatabase().compileStatement(
"SELECT " + COLUMN_TIME +
" FROM " + TABLE_NAME +
" WHERE stream=? AND address=? AND port=?"
);
loadExistingStatement.set(statement);
}
statement.bindLong(1, node.getStream());
statement.bindBlob(2, node.getIPv6());
statement.bindLong(3, node.getPort());
try {
return statement.simpleQueryForLong();
} catch (SQLiteDoneException e) {
return null;
}
}
@Override
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
String[] projection = {
COLUMN_STREAM,
COLUMN_ADDRESS,
COLUMN_PORT,
COLUMN_SERVICES,
COLUMN_TIME
};
List<NetworkAddress> result = new LinkedList<>();
SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query(
TABLE_NAME, projection,
"stream IN (?)",
new String[]{SqlStrings.join(streams).toString()},
null, null,
"time DESC",
valueOf(limit)
)) {
while (c.moveToNext()) {
result.add(
new NetworkAddress.Builder()
.stream(c.getLong(c.getColumnIndex(COLUMN_STREAM)))
.ipv6(c.getBlob(c.getColumnIndex(COLUMN_ADDRESS)))
.port(c.getInt(c.getColumnIndex(COLUMN_PORT)))
.services(c.getLong(c.getColumnIndex(COLUMN_SERVICES)))
.time(c.getLong(c.getColumnIndex(COLUMN_TIME)))
.build()
);
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
throw new ApplicationException(e);
}
if (result.isEmpty()) {
synchronized (this) {
if (stableNodes == null) {
stableNodes = loadStableNodes();
}
}
for (long stream : streams) {
Set<NetworkAddress> nodes = stableNodes.get(stream);
if (nodes != null && !nodes.isEmpty()) {
result.add(Collections.selectRandom(nodes));
}
}
}
return result;
}
@Override
public void offerAddresses(List<NetworkAddress> nodes) {
SQLiteDatabase db = sql.getWritableDatabase();
db.beginTransaction();
try {
cleanUp();
for (NetworkAddress node : nodes) {
if (node.getTime() < now(+5 * MINUTE) && node.getTime() > now(-28 * DAY)) {
synchronized (this) {
Long existing = loadExistingTime(node);
if (existing == null) {
insert(node);
} else if (node.getTime() > existing) {
update(node);
}
}
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
private void insert(NetworkAddress node) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(COLUMN_STREAM, node.getStream());
values.put(COLUMN_ADDRESS, node.getIPv6());
values.put(COLUMN_PORT, node.getPort());
values.put(COLUMN_SERVICES, node.getServices());
values.put(COLUMN_TIME, node.getTime());
db.insertOrThrow(TABLE_NAME, null, values);
} catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e);
}
}
private void update(NetworkAddress node) {
try {
SQLiteDatabase db = sql.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(COLUMN_SERVICES, node.getServices());
values.put(COLUMN_TIME, node.getTime());
db.update(TABLE_NAME, values,
"stream=" + node.getStream() + " AND address=X'" + hex(node.getIPv6()) + "' AND " +
"port=" + node.getPort(),
null);
} catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e);
}
}
}

View File

@ -25,7 +25,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -37,12 +36,13 @@ import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.Strings; import ch.dissem.bitmessage.utils.Strings;
import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static ch.dissem.bitmessage.utils.Singleton.cryptography;
import static ch.dissem.bitmessage.utils.Strings.hex;
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, InternalContext public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, InternalContext
.ContextHolder { .ContextHolder {
private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class); private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class);
private static final String TABLE_NAME = "POW"; private static final String TABLE_NAME = "POW";
@ -71,46 +71,45 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
// you will actually use after this query. // you will actually use after this query.
String[] projection = { String[] projection = {
COLUMN_DATA, COLUMN_DATA,
COLUMN_VERSION, COLUMN_VERSION,
COLUMN_NONCE_TRIALS_PER_BYTE, COLUMN_NONCE_TRIALS_PER_BYTE,
COLUMN_EXTRA_BYTES, COLUMN_EXTRA_BYTES,
COLUMN_EXPIRATION_TIME, COLUMN_EXPIRATION_TIME,
COLUMN_MESSAGE_ID COLUMN_MESSAGE_ID
}; };
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
"initial_hash = X'" + Strings.hex(initialHash) + "'", "initial_hash=X'" + hex(initialHash) + "'",
null, null, null, null null, null, null, null
)) { )) {
c.moveToFirst(); if (c.moveToFirst()) {
if (!c.isAfterLast()) {
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION)); int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)); byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
if (c.isNull(c.getColumnIndex(COLUMN_MESSAGE_ID))) { if (c.isNull(c.getColumnIndex(COLUMN_MESSAGE_ID))) {
return new Item( return new Item(
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
.length), .length),
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)), c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)) c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
); );
} else { } else {
return new Item( return new Item(
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
.length), .length),
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)), c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)), c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)),
c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)), c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)),
bmc.getMessageRepository().getMessage( bmc.getMessageRepository().getMessage(
c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID))) c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID)))
); );
} }
} }
} }
throw new RuntimeException("Object requested that we don't have. Initial hash: " + throw new RuntimeException("Object requested that we don't have. Initial hash: " +
Strings.hex(initialHash)); hex(initialHash));
} }
@Override @Override
@ -118,20 +117,18 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
// you will actually use after this query. // you will actually use after this query.
String[] projection = { String[] projection = {
COLUMN_INITIAL_HASH COLUMN_INITIAL_HASH
}; };
SQLiteDatabase db = sql.getReadableDatabase(); SQLiteDatabase db = sql.getReadableDatabase();
List<byte[]> result = new LinkedList<>(); List<byte[]> result = new LinkedList<>();
try (Cursor c = db.query( try (Cursor c = db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
null, null, null, null, null null, null, null, null, null
)) { )) {
c.moveToFirst(); while (c.moveToNext()) {
while (!c.isAfterLast()) {
byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH)); byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH));
result.add(initialHash); result.add(initialHash);
c.moveToNext();
} }
} }
return result; return result;
@ -156,8 +153,6 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte
db.insertOrThrow(TABLE_NAME, null, values); db.insertOrThrow(TABLE_NAME, null, values);
} catch (SQLiteConstraintException e) { } catch (SQLiteConstraintException e) {
LOG.trace(e.getMessage(), e); LOG.trace(e.getMessage(), e);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} }
} }
@ -169,8 +164,10 @@ public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, Inte
@Override @Override
public void removeObject(byte[] initialHash) { public void removeObject(byte[] initialHash) {
SQLiteDatabase db = sql.getWritableDatabase(); SQLiteDatabase db = sql.getWritableDatabase();
db.delete(TABLE_NAME, db.delete(
"initial_hash = X'" + Strings.hex(initialHash) + "'", TABLE_NAME,
null); "initial_hash=X'" + hex(initialHash) + "'",
null
);
} }
} }

View File

@ -19,6 +19,7 @@ package ch.dissem.apps.abit.repository;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import ch.dissem.apps.abit.util.Assets; import ch.dissem.apps.abit.util.Assets;
/** /**
@ -26,7 +27,7 @@ import ch.dissem.apps.abit.util.Assets;
*/ */
public class SqlHelper extends SQLiteOpenHelper { public class SqlHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version. // If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 4; public static final int DATABASE_VERSION = 5;
public static final String DATABASE_NAME = "jabit.db"; public static final String DATABASE_NAME = "jabit.db";
protected final Context ctx; protected final Context ctx;
@ -38,7 +39,7 @@ public class SqlHelper extends SQLiteOpenHelper {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, 2); onUpgrade(db, 0, DATABASE_VERSION);
} }
@Override @Override
@ -56,8 +57,11 @@ public class SqlHelper extends SQLiteOpenHelper {
case 3: case 3:
executeMigration(db, "V3.1__Update_table_POW"); executeMigration(db, "V3.1__Update_table_POW");
executeMigration(db, "V3.2__Update_table_message"); executeMigration(db, "V3.2__Update_table_message");
case 4:
executeMigration(db, "V3.3__Create_table_node");
default: default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than DATABASE_VERSION. // Nothing to do. Let's assume we won't upgrade from a version that's newer than
// DATABASE_VERSION.
} }
} }

View File

@ -28,16 +28,17 @@ import ch.dissem.apps.abit.pow.ServerPowEngine;
import ch.dissem.apps.abit.repository.AndroidAddressRepository; import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.repository.AndroidInventory; import ch.dissem.apps.abit.repository.AndroidInventory;
import ch.dissem.apps.abit.repository.AndroidMessageRepository; import ch.dissem.apps.abit.repository.AndroidMessageRepository;
import ch.dissem.apps.abit.repository.AndroidNodeRegistry;
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository; import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository;
import ch.dissem.apps.abit.repository.SqlHelper; import ch.dissem.apps.abit.repository.SqlHelper;
import ch.dissem.apps.abit.util.Constants; import ch.dissem.apps.abit.util.Constants;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.networking.nio.NioNetworkHandler;
import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.AddressRepository;
import ch.dissem.bitmessage.ports.MemoryNodeRegistry;
import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository; import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import ch.dissem.bitmessage.utils.TTL;
import static ch.dissem.bitmessage.utils.UnixTime.DAY; import static ch.dissem.bitmessage.utils.UnixTime.DAY;
@ -58,6 +59,7 @@ public class Singleton {
final Context ctx = context.getApplicationContext(); final Context ctx = context.getApplicationContext();
SqlHelper sqlHelper = new SqlHelper(ctx); SqlHelper sqlHelper = new SqlHelper(ctx);
powRepo = new AndroidProofOfWorkRepository(sqlHelper); powRepo = new AndroidProofOfWorkRepository(sqlHelper);
TTL.pubkey(2 * DAY);
bitmessageContext = new BitmessageContext.Builder() bitmessageContext = new BitmessageContext.Builder()
.proofOfWorkEngine(new SwitchingProofOfWorkEngine( .proofOfWorkEngine(new SwitchingProofOfWorkEngine(
ctx, Constants.PREFERENCE_SERVER_POW, ctx, Constants.PREFERENCE_SERVER_POW,
@ -65,15 +67,14 @@ public class Singleton {
new ServicePowEngine(ctx) new ServicePowEngine(ctx)
)) ))
.cryptography(new AndroidCryptography()) .cryptography(new AndroidCryptography())
.nodeRegistry(new MemoryNodeRegistry()) .nodeRegistry(new AndroidNodeRegistry(sqlHelper))
.inventory(new AndroidInventory(sqlHelper)) .inventory(new AndroidInventory(sqlHelper))
.addressRepo(new AndroidAddressRepository(sqlHelper)) .addressRepo(new AndroidAddressRepository(sqlHelper))
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx)) .messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
.powRepo(powRepo) .powRepo(powRepo)
.networkHandler(new DefaultNetworkHandler()) .networkHandler(new NioNetworkHandler())
.listener(getMessageListener(ctx)) .listener(getMessageListener(ctx))
.doNotSendPubkeyOnIdentityCreation() .doNotSendPubkeyOnIdentityCreation()
.pubkeyTTL(2 * DAY)
.build(); .build();
} }
} }

View File

@ -101,7 +101,7 @@ public class Preferences {
} }
public static boolean isConnectionAllowed(Context ctx) { public static boolean isConnectionAllowed(Context ctx) {
return !isWifiOnly(ctx) || WifiReceiver.isConnectedToWifi(ctx); return !isWifiOnly(ctx) || !WifiReceiver.isConnectedToMeteredNetwork(ctx);
} }
public static boolean isWifiOnly(Context ctx) { public static boolean isWifiOnly(Context ctx) {

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="AppTheme.Base">
<!-- Main theme colors -->
<!-- your app branding color for the app bar -->
<item name="android:colorPrimary">@color/colorPrimary</item>
<!-- darker variant for the status bar and contextual app bars -->
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
<!-- theme UI controls like checkboxes and text fields -->
<item name="android:colorAccent">@color/colorAccent</item>
<item name="android:windowContentTransitions">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
<item name="android:windowSharedElementExitTransition">@android:transition/move</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<!--<item name="android:statusBarColor">@android:color/transparent</item>-->
</style>
</resources>

View File

@ -1,13 +1,48 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Palette generated by Material Palette - materialpalette.com/blue-grey/orange --> <!-- Palette generated by Material Palette - materialpalette.com/blue-grey/orange -->
<resources> <resources>
<color name="colorPrimary">#FFC107</color> <color name="colorPrimary">#FFC107</color>
<color name="colorPrimaryDark">#FFA000</color> <color name="colorPrimaryDark">#FFA000</color>
<color name="colorPrimaryDarkText">#DEFFFFFF</color> <color name="colorPrimaryDarkText">#DEFFFFFF</color>
<color name="colorPrimaryLight">#FFECB3</color> <color name="colorPrimaryLight">#FFECB3</color>
<color name="colorAccent">#607D8B</color> <color name="colorAccent">#607D8B</color>
<color name="colorPrimaryText">#212121</color> <color name="colorPrimaryText">#212121</color>
<color name="colorSecondaryText">#727272</color> <color name="colorSecondaryText">#727272</color>
<color name="icons">#212121</color> <color name="icons">#212121</color>
<color name="divider">#B6B6B6</color> <color name="divider">#B6B6B6</color>
<!-- Material DEFAULT colors -->
<color name="material_drawer_primary">@color/colorPrimary</color>
<color name="material_drawer_primary_dark">@color/colorPrimaryDark</color>
<color name="material_drawer_primary_light">@color/colorPrimaryLight</color>
<color name="material_drawer_accent">@color/colorAccent</color>
<!-- OVERWRITE THESE COLORS FOR A LIGHT THEME -->
<!-- MaterialDrawer DEFAULT colors -->
<color name="material_drawer_background">@color/colorPrimaryDark</color>
<!-- Material DEFAULT text / items colors -->
<color name="material_drawer_primary_text">@color/colorPrimaryText</color>
<color name="material_drawer_primary_icon">@color/icons</color>
<color name="material_drawer_secondary_text">@color/colorSecondaryText</color>
<color name="material_drawer_hint_text">@color/colorSecondaryText</color>
<color name="material_drawer_divider">@color/divider</color>
<!-- Material DEFAULT drawer colors -->
<color name="material_drawer_selected">@color/primary</color>
<color name="material_drawer_selected_text">@color/colorPrimaryText</color>
<color name="material_drawer_header_selection_text">@color/colorPrimaryText</color>
<!-- OVERWRITE THESE COLORS FOR A DARK THEME -->
<!-- MaterialDrawer DEFAULT DARK colors -->
<color name="material_drawer_dark_background">#303030</color>
<!-- MaterialDrawer DEFAULT DARK text / items colors -->
<color name="material_drawer_dark_primary_text">#DEFFFFFF</color>
<color name="material_drawer_dark_primary_icon">#8AFFFFFF</color>
<color name="material_drawer_dark_secondary_text">#8AFFFFFF</color>
<color name="material_drawer_dark_hint_text">#42FFFFFF</color>
<color name="material_drawer_dark_divider">#1FFFFFFF</color>
<!-- MaterialDrawer DEFAULT DARK drawer colors -->
<color name="material_drawer_dark_selected">#202020</color>
<color name="material_drawer_dark_selected_text">@color/material_drawer_primary</color>
<color name="material_drawer_dark_header_selection_text">#FFF</color>
</resources> </resources>

View File

@ -1,28 +1,12 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme.Base" parent="MaterialDrawerTheme.Light.DarkToolbar.TranslucentStatus"> <style name="AppTheme" parent="MaterialDrawerTheme.Light.DarkToolbar.TranslucentStatus">
<item name="android:activatedBackgroundIndicator">@color/colorPrimaryLight</item> <item name="android:activatedBackgroundIndicator">@color/colorPrimaryLight</item>
<item name="android:textColor">@color/colorPrimaryText</item> <item name="android:textColor">@color/colorPrimaryText</item>
<item name="android:textColorSecondary">@color/colorSecondaryText</item> <item name="android:textColorSecondary">@color/colorSecondaryText</item>
<!-- MaterialDrawer specific values -->
<item name="material_drawer_background">@color/colorPrimaryDark</item>
<item name="material_drawer_icons">@color/colorPrimaryText</item>
<item name="material_drawer_primary_icon">@color/icons</item>
<item name="material_drawer_primary_text">@color/colorPrimaryText</item>
<item name="material_drawer_secondary_text">@color/colorSecondaryText</item>
<item name="material_drawer_hint_text">@color/colorSecondaryText</item>
<item name="material_drawer_divider">@color/divider</item>
<item name="material_drawer_selected">@color/primary</item>
<item name="material_drawer_selected_text">@color/colorPrimaryText</item>
<item name="material_drawer_header_selection_text">@color/colorPrimaryText</item>
</style> </style>
<style name="AppTheme" parent="AppTheme.Base"/>
<style name="CustomShowcaseTheme" parent="ShowcaseView"> <style name="CustomShowcaseTheme" parent="ShowcaseView">
<item name="sv_backgroundColor">#eeffc107</item> <item name="sv_backgroundColor">#eeffc107</item>
<item name="sv_showcaseColor">#ffc107</item> <item name="sv_showcaseColor">#ffc107</item>

View File

@ -9,7 +9,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.1.2' classpath 'com.android.tools.build:gradle:2.1.3'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files