UI improvements and fixes for older Android versions

This commit is contained in:
Christian Basler 2017-07-10 06:21:29 +02:00
parent 1c284eba26
commit 433c757107
27 changed files with 352 additions and 192 deletions

View File

@ -1,5 +1,5 @@
apply plugin: 'idea'
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'idea'
ext { ext {
appName = "Abit" appName = "Abit"
@ -44,6 +44,7 @@ dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.android.support:appcompat-v7:$supportVersion" compile "com.android.support:appcompat-v7:$supportVersion"
compile "com.android.support:preference-v7:$supportVersion"
compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:support-v4:$supportVersion"
compile "com.android.support:design:$supportVersion" compile "com.android.support:design:$supportVersion"
compile "com.android.support:multidex:1.0.1" compile "com.android.support:multidex:1.0.1"

View File

@ -84,16 +84,6 @@
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".SettingsActivity"
android:label="@string/settings"
android:parentActivityName=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity <activity
android:name=".CreateAddressActivity" android:name=".CreateAddressActivity"
android:label="@string/title_activity_open_bitmessage_link" android:label="@string/title_activity_open_bitmessage_link"
@ -182,10 +172,10 @@
<activity <activity
android:name=".StatusActivity" android:name=".StatusActivity"
android:label="@string/title_activity_status" android:label="@string/title_activity_status"
android:parentActivityName=".SettingsActivity"> android:parentActivityName=".MainActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".SettingsActivity"/> android:value=".MainActivity"/>
</activity> </activity>
</application> </application>

View File

@ -27,7 +27,7 @@ import ch.dissem.apps.abit.listener.ListSelectionListener;
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
public abstract class AbstractItemListFragment<T> extends ListFragment implements ListHolder { public abstract class AbstractItemListFragment<L, T> extends ListFragment implements ListHolder<L> {
/** /**
* The serialization (saved instance state) Bundle key representing the * The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets. * activated item position. Only used on tablets.
@ -143,4 +143,14 @@ public abstract class AbstractItemListFragment<T> extends ListFragment implement
activatedPosition = position; activatedPosition = position;
} }
@Override
public L getCurrentLabel() {
return null;
}
@Override
public boolean showPreviousList() {
return false;
}
} }

View File

@ -47,7 +47,7 @@ import io.github.yavski.fabspeeddial.SimpleMenuListenerAdapter;
/** /**
* Fragment that shows a list of all contacts, the ones we subscribed to first. * Fragment that shows a list of all contacts, the ones we subscribed to first.
*/ */
public class AddressListFragment extends AbstractItemListFragment<BitmessageAddress> { public class AddressListFragment extends AbstractItemListFragment<Void, BitmessageAddress> {
private ArrayAdapter<BitmessageAddress> adapter; private ArrayAdapter<BitmessageAddress> adapter;
@Override @Override
@ -166,7 +166,7 @@ public class AddressListFragment extends AbstractItemListFragment<BitmessageAddr
} }
@Override @Override
public void updateList(Label label) { public void updateList(Void label) {
updateList(); updateList();
} }

View File

@ -16,13 +16,15 @@
package ch.dissem.apps.abit; package ch.dissem.apps.abit;
import ch.dissem.bitmessage.entity.valueobject.Label;
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
public interface ListHolder { public interface ListHolder<L> {
void updateList(Label label); void updateList(L label);
void setActivateOnItemClick(boolean activateOnItemClick); void setActivateOnItemClick(boolean activateOnItemClick);
L getCurrentLabel();
boolean showPreviousList();
} }

View File

@ -21,6 +21,7 @@ import android.graphics.Point;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity; 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;
@ -195,11 +196,15 @@ public class MainActivity extends AppCompatActivity
} }
private <F extends Fragment & ListHolder> void changeList(F listFragment) { private <F extends Fragment & ListHolder> void changeList(F listFragment) {
getSupportFragmentManager()
.beginTransaction() FragmentTransaction transaction = getSupportFragmentManager()
.replace(R.id.item_list, listFragment) .beginTransaction();
.addToBackStack(null) transaction.replace(R.id.item_list, listFragment);
.commit(); Fragment detailFragment = getSupportFragmentManager().findFragmentById(R.id.message_detail_container);
if (detailFragment != null) {
transaction.remove(detailFragment);
}
transaction.addToBackStack(null).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
@ -325,15 +330,31 @@ public class MainActivity extends AppCompatActivity
}.execute(); }.execute();
} }
@Override
public void onBackPressed() {
Fragment listFragment = getSupportFragmentManager().findFragmentById(R.id.item_list);
if (listFragment instanceof ListHolder) {
ListHolder listHolder = (ListHolder) listFragment;
if (listHolder.showPreviousList()) {
IDrawerItem drawerItem = drawer.getDrawerItem(listHolder.getCurrentLabel());
if (drawerItem != null){
drawer.setSelection(drawerItem);
}
return;
}
}
super.onBackPressed();
}
private class DrawerItemClickListener implements Drawer.OnDrawerItemClickListener { private class DrawerItemClickListener implements Drawer.OnDrawerItemClickListener {
@Override @Override
public boolean onItemClick(View view, int position, IDrawerItem item) { public boolean onItemClick(View view, int position, IDrawerItem item) {
Fragment itemList = getSupportFragmentManager().findFragmentById(R.id.item_list);
if (item.getTag() instanceof Label) { if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag(); selectedLabel = (Label) item.getTag();
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof if (itemList instanceof
MessageListFragment) { MessageListFragment) {
((MessageListFragment) getSupportFragmentManager() ((MessageListFragment) itemList).updateList(selectedLabel);
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
} else { } else {
MessageListFragment listFragment = new MessageListFragment(); MessageListFragment listFragment = new MessageListFragment();
changeList(listFragment); changeList(listFragment);
@ -344,17 +365,18 @@ public class MainActivity extends AppCompatActivity
Nameable<?> ni = (Nameable<?>) item; Nameable<?> ni = (Nameable<?>) item;
switch (ni.getName().getTextRes()) { switch (ni.getName().getTextRes()) {
case R.string.contacts_and_subscriptions: case R.string.contacts_and_subscriptions:
if (!(getSupportFragmentManager().findFragmentById(R.id if (!(itemList instanceof AddressListFragment)) {
.item_list) instanceof AddressListFragment)) {
changeList(new AddressListFragment()); changeList(new AddressListFragment());
} else { } else {
((AddressListFragment) getSupportFragmentManager() ((AddressListFragment) itemList).updateList();
.findFragmentById(R.id.item_list)).updateList();
} }
return false; return false;
case R.string.settings: case R.string.settings:
startActivity(new Intent(MainActivity.this, SettingsActivity getSupportFragmentManager()
.class)); .beginTransaction()
.replace(R.id.item_list, new SettingsFragment())
.addToBackStack(null)
.commit();
return false; return false;
case R.string.full_node: case R.string.full_node:
return true; return true;
@ -501,7 +523,7 @@ public class MainActivity extends AppCompatActivity
Fragment fragment; Fragment fragment;
if (item instanceof Plaintext) { if (item instanceof Plaintext) {
fragment = new MessageDetailFragment(); fragment = new MessageDetailFragment();
} else if (item instanceof String) { } else if (item instanceof BitmessageAddress) {
fragment = new AddressDetailFragment(); fragment = new AddressDetailFragment();
} else { } else {
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " + item.getClass().getSimpleName()); throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " + item.getClass().getSimpleName());
@ -517,7 +539,7 @@ public class MainActivity extends AppCompatActivity
if (item instanceof Plaintext) { if (item instanceof Plaintext) {
detailIntent = new Intent(this, MessageDetailActivity.class); detailIntent = new Intent(this, MessageDetailActivity.class);
detailIntent.putExtra(EXTRA_SHOW_LABEL, selectedLabel); detailIntent.putExtra(EXTRA_SHOW_LABEL, selectedLabel);
} else if (item instanceof String) { } else if (item instanceof BitmessageAddress) {
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 " +
@ -529,6 +551,18 @@ public class MainActivity extends AppCompatActivity
} }
} }
public boolean hasDetailPane() {
return twoPane;
}
public void setDetailView(Fragment fragment) {
if (twoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.message_detail_container, fragment)
.commit();
}
}
@Override @Override
public void updateTitle(CharSequence title) { public void updateTitle(CharSequence title) {
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {

View File

@ -38,10 +38,8 @@ import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeMana
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager; import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Stack;
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter; import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter;
import ch.dissem.apps.abit.listener.ActionBarListener; import ch.dissem.apps.abit.listener.ActionBarListener;
@ -51,7 +49,6 @@ import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
import io.github.yavski.fabspeeddial.FabSpeedDial; import io.github.yavski.fabspeeddial.FabSpeedDial;
import io.github.yavski.fabspeeddial.SimpleMenuListenerAdapter; import io.github.yavski.fabspeeddial.SimpleMenuListenerAdapter;
@ -68,7 +65,7 @@ import static ch.dissem.apps.abit.MessageDetailFragment.isInTrash;
* Activities containing this fragment MUST implement the {@link ListSelectionListener} * Activities containing this fragment MUST implement the {@link ListSelectionListener}
* interface. * interface.
*/ */
public class MessageListFragment extends Fragment implements ListHolder { public class MessageListFragment extends Fragment implements ListHolder<Label> {
private RecyclerView recyclerView; private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager; private RecyclerView.LayoutManager layoutManager;
@ -82,6 +79,8 @@ public class MessageListFragment extends Fragment implements ListHolder {
private AndroidMessageRepository messageRepo; private AndroidMessageRepository messageRepo;
private boolean activateOnItemClick; private boolean activateOnItemClick;
private Stack<Label> backStack = new Stack<>();
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -95,11 +94,18 @@ public class MessageListFragment extends Fragment implements ListHolder {
MainActivity activity = (MainActivity) getActivity(); MainActivity activity = (MainActivity) getActivity();
messageRepo = Singleton.getMessageRepository(activity); messageRepo = Singleton.getMessageRepository(activity);
if (backStack.isEmpty()) {
doUpdateList(activity.getSelectedLabel()); doUpdateList(activity.getSelectedLabel());
} else {
doUpdateList(backStack.peek());
}
} }
@Override @Override
public void updateList(Label label) { public void updateList(Label label) {
if (currentLabel != null && !currentLabel.equals(label)) {
backStack.push(currentLabel);
}
if (!isResumed()) { if (!isResumed()) {
currentLabel = label; currentLabel = label;
return; return;
@ -330,4 +336,19 @@ public class MessageListFragment extends Fragment implements ListHolder {
} }
this.activateOnItemClick = activateOnItemClick; this.activateOnItemClick = activateOnItemClick;
} }
@Override
public boolean showPreviousList() {
if (backStack.isEmpty()) {
return false;
} else {
doUpdateList(backStack.pop());
return true;
}
}
@Override
public Label getCurrentLabel() {
return currentLabel;
}
} }

View File

@ -1,18 +0,0 @@
package ch.dissem.apps.abit;
import android.os.Bundle;
/**
* @author Christian Basler
*/
public class SettingsActivity extends DetailActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(R.id.content, new SettingsFragment())
.commit();
}
}

View File

@ -21,14 +21,15 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.widget.Toast; import android.widget.Toast;
import com.mikepenz.aboutlibraries.Libs; import com.mikepenz.aboutlibraries.Libs;
import com.mikepenz.aboutlibraries.LibsBuilder; import com.mikepenz.aboutlibraries.LibsBuilder;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.service.Singleton; import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.SyncAdapter; import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.BitmessageContext;
@ -40,26 +41,29 @@ import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
* @author Christian Basler * @author Christian Basler
*/ */
public class SettingsFragment public class SettingsFragment
extends PreferenceFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener { implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
Preference about = findPreference("about"); Preference about = findPreference("about");
about.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { about.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
new LibsBuilder() LibsBuilder libsBuilder = new LibsBuilder()
.withActivityTitle(getActivity().getString(R.string.about)) .withActivityTitle(getActivity().getString(R.string.about))
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR) .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true) .withAboutIconShown(true)
.withAboutVersionShown(true) .withAboutVersionShown(true)
.withAboutDescription(getString(R.string.about_app)) .withAboutDescription(getString(R.string.about_app));
.start(getActivity()); MainActivity activity = (MainActivity) getActivity();
if (activity.hasDetailPane()) {
activity.setDetailView(libsBuilder.supportFragment());
} else {
libsBuilder.start(getActivity());
}
return true; return true;
} }
}); });
@ -103,7 +107,12 @@ public class SettingsFragment
status.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { status.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
MainActivity activity = (MainActivity) getActivity();
if (activity.hasDetailPane()) {
activity.setDetailView(new StatusFragment());
} else {
startActivity(new Intent(getActivity(), StatusActivity.class)); startActivity(new Intent(getActivity(), StatusActivity.class));
}
return true; return true;
} }
}); });
@ -114,6 +123,10 @@ public class SettingsFragment
super.onAttach(ctx); super.onAttach(ctx);
PreferenceManager.getDefaultSharedPreferences(ctx) PreferenceManager.getDefaultSharedPreferences(ctx)
.registerOnSharedPreferenceChangeListener(this); .registerOnSharedPreferenceChangeListener(this);
if (ctx instanceof ActionBarListener) {
((ActionBarListener) ctx).updateTitle(getString(R.string.settings));
}
} }
@Override @Override

View File

@ -0,0 +1,49 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
public class StatusFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_status, container, false);
BitmessageContext bmc = Singleton.getBitmessageContext(inflater.getContext());
StringBuilder status = new StringBuilder();
for (BitmessageAddress address : bmc.addresses().getIdentities()) {
status.append(address.getAddress()).append('\n');
}
status.append('\n');
status.append(bmc.status());
((TextView) view.findViewById(R.id.content)).setText(status);
return view;
}
}

View File

@ -30,13 +30,11 @@ import android.widget.TextView;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter; import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants; import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction; import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionMoveToSwipedDirection;
.SwipeResultActionMoveToSwipedDirection;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem; import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder; import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
import com.h6ah4i.android.widget.advrecyclerview.utils.RecyclerViewAdapterUtils; import com.h6ah4i.android.widget.advrecyclerview.utils.RecyclerViewAdapterUtils;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -66,7 +64,7 @@ public class SwipeableMessageAdapter
private final View.OnClickListener swipeableViewContainerOnClickListener; private final View.OnClickListener swipeableViewContainerOnClickListener;
private Label label; private Label label;
private int selectedPosition; private int selectedPosition = -1;
private boolean activateOnItemClick; private boolean activateOnItemClick;
public void setActivateOnItemClick(boolean activateOnItemClick) { public void setActivateOnItemClick(boolean activateOnItemClick) {

View File

@ -303,11 +303,10 @@ public class AndroidAddressRepository implements AddressRepository {
} }
} }
@NonNull
@Override @Override
public BitmessageAddress getAddress(String address) { public BitmessageAddress getAddress(String address) {
List<BitmessageAddress> result = find("address = '" + address + "'"); List<BitmessageAddress> result = find("address = '" + address + "'");
if (result.size() > 0) return result.get(0); if (result.size() > 0) return result.get(0);
return new BitmessageAddress(address); return null;
} }
} }

View File

@ -35,6 +35,7 @@ import java.util.UUID;
import ch.dissem.apps.abit.util.Labels; import ch.dissem.apps.abit.util.Labels;
import ch.dissem.apps.abit.util.UuidUtils; import ch.dissem.apps.abit.util.UuidUtils;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.Label;
@ -154,29 +155,30 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
@Override @Override
public int countUnread(Label label) { public int countUnread(Label label) {
String[] args;
String where;
if (label == null) {
return 0;
}
if (label == LABEL_ARCHIVE) { if (label == LABEL_ARCHIVE) {
where = ""; return 0;
args = new String[]{ } else if (label == null) {
Label.Type.UNREAD.name() return (int) DatabaseUtils.queryNumEntries(
}; sql.getReadableDatabase(),
} else { TABLE_NAME,
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=?) AND "; "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
args = new String[]{ new String[]{
String.valueOf(label.getId()), String.valueOf(label.getId()),
Label.Type.UNREAD.name() Label.Type.UNREAD.name()
};
} }
SQLiteDatabase db = sql.getReadableDatabase();
return (int) DatabaseUtils.queryNumEntries(db, TABLE_NAME,
where + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" +
"SELECT id FROM Label WHERE type=?))",
args
); );
} else {
return (int) DatabaseUtils.queryNumEntries(
sql.getReadableDatabase(),
TABLE_NAME,
" id IN (SELECT message_id FROM Message_Label WHERE label_id=?) " +
"AND id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
new String[]{
String.valueOf(label.getId()),
Label.Type.UNREAD.name()
}
);
}
} }
@NonNull @NonNull
@ -249,7 +251,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} }
@NonNull @NonNull
protected List<Long> findIds(String where) { private List<Long> findIds(String where) {
List<Long> result = new LinkedList<>(); List<Long> result = new LinkedList<>();
// Define a projection that specifies which columns from the database // Define a projection that specifies which columns from the database
@ -266,7 +268,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
COLUMN_RECEIVED + " DESC, " + COLUMN_SENT + " DESC" COLUMN_RECEIVED + " DESC, " + COLUMN_SENT + " DESC"
)) { )) {
while (c.moveToNext()) { while (c.moveToNext()) {
long id = c.getLong(c.getColumnIndex(COLUMN_ID)); long id = c.getLong(0);
result.add(id); result.add(id);
} }
} }
@ -315,11 +317,21 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
builder.IV(InventoryVector.fromHash(iv)); builder.IV(InventoryVector.fromHash(iv));
String sender = c.getString(c.getColumnIndex(COLUMN_SENDER)); String sender = c.getString(c.getColumnIndex(COLUMN_SENDER));
if (sender != null) { if (sender != null) {
builder.from(ctx.getAddressRepository().getAddress(sender)); BitmessageAddress address = ctx.getAddressRepository().getAddress(sender);
if (address != null) {
builder.from(address);
} else {
builder.from(new BitmessageAddress(sender));
}
} }
String recipient = c.getString(c.getColumnIndex(COLUMN_RECIPIENT)); String recipient = c.getString(c.getColumnIndex(COLUMN_RECIPIENT));
if (recipient != null) { if (recipient != null) {
builder.to(ctx.getAddressRepository().getAddress(recipient)); BitmessageAddress address = ctx.getAddressRepository().getAddress(recipient);
if (address != null) {
builder.to(address);
} else {
builder.to(new BitmessageAddress(sender));
}
} }
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)));
@ -405,7 +417,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
} }
private void update(SQLiteDatabase db, Plaintext message) { private void update(SQLiteDatabase db, Plaintext message) {
db.update(TABLE_NAME, getValues(message), "id = " + message.getId(), null); db.update(TABLE_NAME, getValues(message), "id=?", new String[]{valueOf(message.getId())});
} }
@Override @Override

View File

@ -16,11 +16,16 @@
package ch.dissem.apps.abit.repository; package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import java.util.UUID;
import ch.dissem.apps.abit.util.Assets; import ch.dissem.apps.abit.util.Assets;
import ch.dissem.apps.abit.util.UuidUtils;
/** /**
* Handles database migration and provides access. * Handles database migration and provides access.
@ -63,12 +68,37 @@ public class SqlHelper extends SQLiteOpenHelper {
executeMigration(db, "V3.4__Add_label_outbox"); executeMigration(db, "V3.4__Add_label_outbox");
case 6: case 6:
executeMigration(db, "V4.0__Create_table_message_parent"); executeMigration(db, "V4.0__Create_table_message_parent");
case 7:
setMissingConversationIds(db);
default: default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than // Nothing to do. Let's assume we won't upgrade from a version that's newer than
// DATABASE_VERSION. // DATABASE_VERSION.
} }
} }
/**
* Set UUIDs for all messages that have no conversation ID
*/
private void setMissingConversationIds(SQLiteDatabase db) {
try (Cursor c = db.query(
"Message", new String[]{"id"},
"conversation IS NULL",
null, null, null, null
)) {
while (c.moveToNext()) {
long id = c.getLong(0);
setMissingConversationId(id, db);
}
}
}
private void setMissingConversationId(long id, SQLiteDatabase db) {
ContentValues values = new ContentValues(1);
values.put("conversation", UuidUtils.asBytes(UUID.randomUUID()));
db.update("Message", values, "id=?", new String[]{String.valueOf(id)});
}
private void executeMigration(SQLiteDatabase db, String name) { private void executeMigration(SQLiteDatabase db, String name) {
for (String statement : Assets.readSqlStatements(ctx, "db/migration/" + name + ".sql")) { for (String statement : Assets.readSqlStatements(ctx, "db/migration/" + name + ".sql")) {
db.execSQL(statement); db.execSQL(statement);

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_item_selected_state"
android:state_pressed="true"
android:state_selected="true"
android:state_activated="true"
android:state_active="true"
android:state_checked="true"
android:state_focused="true"
android:state_single="true"/>
<item android:drawable="@drawable/bg_item_normal_state" />
</selector>

View File

@ -1,21 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 Haruki Hasegawa
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item> <item>
<color android:color="@color/bg_item_selected_state"/> <shape android:shape="rectangle">
<solid android:color="@color/colorAccent" />
</shape>
</item>
<item android:left="4dp">
<shape android:shape="rectangle">
<solid android:color="@color/colorAccentLight" />
</shape>
</item> </item>
</layer-list> </layer-list>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/md_white_1000" />
</shape>
</item>
</layer-list>

View File

@ -1,4 +1,4 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -7,53 +7,55 @@
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:elevation="4dp" android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
tools:ignore="UnusedAttribute"/> tools:ignore="UnusedAttribute"
tools:layout_editor_absoluteX="8dp" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.Guideline
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/guideline"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_below="@id/toolbar" android:orientation="vertical"
android:baselineAligned="false" app:layout_constraintGuide_percent="0.382" />
android:orientation="horizontal"
android:showDividers="middle"
tools:context=".MainActivity">
<!--
This layout is a two-pane layout for the Messages
master/detail flow.
-->
<FrameLayout <FrameLayout
android:id="@+id/item_list" android:id="@+id/item_list"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_weight="1" android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/toolbar"
android:background="@drawable/bg_white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
tools:context=".MessageListActivity" tools:context=".MessageListActivity"
tools:layout="@layout/fragment_message_list"/> tools:layout="@layout/fragment_message_list"
tools:ignore="UnusedAttribute" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:background="@color/bg_item_selected_state">
<FrameLayout <FrameLayout
android:id="@+id/message_detail_container" android:id="@+id/message_detail_container"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_margin="16dp" android:layout_below="@+id/toolbar"
android:background="@color/contentBackground" android:layout_weight="2"
android:elevation="2dp" android:background="@drawable/bg_white"
tools:layout="@layout/fragment_message_detail" android:padding="8dp"
tools:ignore="InconsistentLayout,UnusedAttribute"/> app:layout_constraintBottom_toBottomOf="parent"
</FrameLayout> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
tools:ignore="InconsistentLayout"
tools:layout="@layout/fragment_message_detail" />
</LinearLayout> </android.support.constraint.ConstraintLayout>
</RelativeLayout>

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Copyright 2015 Christian Basler ~ Copyright 2015 Christian Basler
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
@ -18,7 +17,8 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:background="@drawable/bg_item_selectable">
<ImageView <ImageView
android:id="@+id/avatar" android:id="@+id/avatar"
@ -28,7 +28,7 @@
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_margin="16dp" android:layout_margin="16dp"
android:src="@color/colorAccent" android:src="@color/colorAccent"
tools:ignore="ContentDescription"/> tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
@ -44,7 +44,7 @@
android:paddingTop="0dp" android:paddingTop="0dp"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textStyle="bold" android:textStyle="bold"
tools:text="Name"/> tools:text="Name" />
<TextView <TextView
android:id="@+id/address" android:id="@+id/address"
@ -57,6 +57,6 @@
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="BM-2cW0000000000000000000000000000000"/> tools:text="BM-2cW0000000000000000000000000000000" />
</RelativeLayout> </RelativeLayout>

View File

@ -13,8 +13,9 @@
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:choiceMode="singleChoice" android:choiceMode="singleChoice"
android:clipToPadding="false" android:clipToPadding="false"
android:listSelector="@drawable/bg_item_selected_state"
android:paddingBottom="88dp" android:paddingBottom="88dp"
android:scrollbarStyle="outsideOverlay"/> android:scrollbarStyle="outsideOverlay" />
<io.github.yavski.fabspeeddial.FabSpeedDial <io.github.yavski.fabspeeddial.FabSpeedDial
android:id="@+id/fab_add_contact" android:id="@+id/fab_add_contact"
@ -27,5 +28,5 @@
app:elevation="8dp" app:elevation="8dp"
app:fabDrawable="@drawable/ic_action_add_contact" app:fabDrawable="@drawable/ic_action_add_contact"
app:fabGravity="bottom_end" app:fabGravity="bottom_end"
app:fabMenu="@menu/fab_address"/> app:fabMenu="@menu/fab_address" />
</RelativeLayout> </RelativeLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="monospace"
android:padding="16dp"
android:text=""
android:textIsSelectable="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

View File

@ -19,8 +19,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:background="?android:attr/activatedBackgroundIndicator">
<ImageView <ImageView
android:id="@+id/avatar" android:id="@+id/avatar"

View File

@ -6,6 +6,7 @@
<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="colorAccentLight">#d3e0e6</color>
<color name="colorPrimaryText">#212121</color> <color name="colorPrimaryText">#212121</color>
<color name="colorSecondaryText">#727272</color> <color name="colorSecondaryText">#727272</color>
<color name="contentBackground">#FFFFFF</color> <color name="contentBackground">#FFFFFF</color>

View File

@ -5,6 +5,7 @@
<item name="android:activatedBackgroundIndicator">@drawable/bg_item_activated</item> <item name="android:activatedBackgroundIndicator">@drawable/bg_item_activated</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>
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style> </style>
<style name="CustomShowcaseTheme" parent="ShowcaseView"> <style name="CustomShowcaseTheme" parent="ShowcaseView">

View File

@ -1,14 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- <SwitchPreferenceCompat
<ListPreference
android:key="wifi_mode"
android:title="@string/wifi_mode"
android:dialogTitle="@string/wifi_mode"
android:entries="@array/connection_modes"
android:entryValues="@array/connection_mode_values"/>
-->
<SwitchPreference
android:defaultValue="true" android:defaultValue="true"
android:key="wifi_only" android:key="wifi_only"
android:summary="@string/wifi_only_summary" android:summary="@string/wifi_only_summary"
@ -24,7 +16,7 @@
android:key="sync_timeout" android:key="sync_timeout"
android:summary="@string/sync_timeout_summary" android:summary="@string/sync_timeout_summary"
android:title="@string/sync_timeout"/> android:title="@string/sync_timeout"/>
<SwitchPreference <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:dependency="trusted_node" android:dependency="trusted_node"
android:key="server_pow" android:key="server_pow"

Binary file not shown.

View File

@ -1,6 +1,6 @@
#Tue Apr 04 00:02:32 CEST 2017 #Wed Jul 05 00:16:56 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip