UI improvements and fixes for older Android versions

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

View File

@ -27,7 +27,7 @@ import ch.dissem.apps.abit.listener.ListSelectionListener;
/**
* @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
* activated item position. Only used on tablets.
@ -143,4 +143,14 @@ public abstract class AbstractItemListFragment<T> extends ListFragment implement
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.
*/
public class AddressListFragment extends AbstractItemListFragment<BitmessageAddress> {
public class AddressListFragment extends AbstractItemListFragment<Void, BitmessageAddress> {
private ArrayAdapter<BitmessageAddress> adapter;
@Override
@ -166,7 +166,7 @@ public class AddressListFragment extends AbstractItemListFragment<BitmessageAddr
}
@Override
public void updateList(Label label) {
public void updateList(Void label) {
updateList();
}

View File

@ -16,13 +16,15 @@
package ch.dissem.apps.abit;
import ch.dissem.bitmessage.entity.valueobject.Label;
/**
* @author Christian Basler
*/
public interface ListHolder {
void updateList(Label label);
public interface ListHolder<L> {
void updateList(L label);
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.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
@ -195,11 +196,15 @@ public class MainActivity extends AppCompatActivity
}
private <F extends Fragment & ListHolder> void changeList(F listFragment) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.item_list, listFragment)
.addToBackStack(null)
.commit();
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.item_list, listFragment);
Fragment detailFragment = getSupportFragmentManager().findFragmentById(R.id.message_detail_container);
if (detailFragment != null) {
transaction.remove(detailFragment);
}
transaction.addToBackStack(null).commit();
if (twoPane) {
// In two-pane mode, list items should be given the
@ -325,15 +330,31 @@ public class MainActivity extends AppCompatActivity
}.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 {
@Override
public boolean onItemClick(View view, int position, IDrawerItem item) {
Fragment itemList = getSupportFragmentManager().findFragmentById(R.id.item_list);
if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag();
if (getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof
if (itemList instanceof
MessageListFragment) {
((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList(selectedLabel);
((MessageListFragment) itemList).updateList(selectedLabel);
} else {
MessageListFragment listFragment = new MessageListFragment();
changeList(listFragment);
@ -344,17 +365,18 @@ public class MainActivity extends AppCompatActivity
Nameable<?> ni = (Nameable<?>) item;
switch (ni.getName().getTextRes()) {
case R.string.contacts_and_subscriptions:
if (!(getSupportFragmentManager().findFragmentById(R.id
.item_list) instanceof AddressListFragment)) {
if (!(itemList instanceof AddressListFragment)) {
changeList(new AddressListFragment());
} else {
((AddressListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list)).updateList();
((AddressListFragment) itemList).updateList();
}
return false;
case R.string.settings:
startActivity(new Intent(MainActivity.this, SettingsActivity
.class));
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.item_list, new SettingsFragment())
.addToBackStack(null)
.commit();
return false;
case R.string.full_node:
return true;
@ -501,7 +523,7 @@ public class MainActivity extends AppCompatActivity
Fragment fragment;
if (item instanceof Plaintext) {
fragment = new MessageDetailFragment();
} else if (item instanceof String) {
} else if (item instanceof BitmessageAddress) {
fragment = new AddressDetailFragment();
} else {
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) {
detailIntent = new Intent(this, MessageDetailActivity.class);
detailIntent.putExtra(EXTRA_SHOW_LABEL, selectedLabel);
} else if (item instanceof String) {
} else if (item instanceof BitmessageAddress) {
detailIntent = new Intent(this, AddressDetailActivity.class);
} else {
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
public void updateTitle(CharSequence title) {
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.utils.WrapperAdapterUtils;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Stack;
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter;
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.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
import io.github.yavski.fabspeeddial.FabSpeedDial;
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}
* interface.
*/
public class MessageListFragment extends Fragment implements ListHolder {
public class MessageListFragment extends Fragment implements ListHolder<Label> {
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
@ -82,6 +79,8 @@ public class MessageListFragment extends Fragment implements ListHolder {
private AndroidMessageRepository messageRepo;
private boolean activateOnItemClick;
private Stack<Label> backStack = new Stack<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -95,11 +94,18 @@ public class MessageListFragment extends Fragment implements ListHolder {
MainActivity activity = (MainActivity) getActivity();
messageRepo = Singleton.getMessageRepository(activity);
doUpdateList(activity.getSelectedLabel());
if (backStack.isEmpty()) {
doUpdateList(activity.getSelectedLabel());
} else {
doUpdateList(backStack.peek());
}
}
@Override
public void updateList(Label label) {
if (currentLabel != null && !currentLabel.equals(label)) {
backStack.push(currentLabel);
}
if (!isResumed()) {
currentLabel = label;
return;
@ -330,4 +336,19 @@ public class MessageListFragment extends Fragment implements ListHolder {
}
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.os.AsyncTask;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.widget.Toast;
import com.mikepenz.aboutlibraries.Libs;
import com.mikepenz.aboutlibraries.LibsBuilder;
import ch.dissem.apps.abit.listener.ActionBarListener;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.bitmessage.BitmessageContext;
@ -40,26 +41,29 @@ import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
* @author Christian Basler
*/
public class SettingsFragment
extends PreferenceFragment
extends PreferenceFragmentCompat
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);
Preference about = findPreference("about");
about.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
new LibsBuilder()
LibsBuilder libsBuilder = new LibsBuilder()
.withActivityTitle(getActivity().getString(R.string.about))
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.withAboutDescription(getString(R.string.about_app))
.start(getActivity());
.withAboutDescription(getString(R.string.about_app));
MainActivity activity = (MainActivity) getActivity();
if (activity.hasDetailPane()) {
activity.setDetailView(libsBuilder.supportFragment());
} else {
libsBuilder.start(getActivity());
}
return true;
}
});
@ -103,7 +107,12 @@ public class SettingsFragment
status.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(getActivity(), StatusActivity.class));
MainActivity activity = (MainActivity) getActivity();
if (activity.hasDetailPane()) {
activity.setDetailView(new StatusFragment());
} else {
startActivity(new Intent(getActivity(), StatusActivity.class));
}
return true;
}
});
@ -114,6 +123,10 @@ public class SettingsFragment
super.onAttach(ctx);
PreferenceManager.getDefaultSharedPreferences(ctx)
.registerOnSharedPreferenceChangeListener(this);
if (ctx instanceof ActionBarListener) {
((ActionBarListener) ctx).updateTitle(getString(R.string.settings));
}
}
@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.SwipeableItemConstants;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action
.SwipeResultActionMoveToSwipedDirection;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionMoveToSwipedDirection;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
import com.h6ah4i.android.widget.advrecyclerview.utils.RecyclerViewAdapterUtils;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@ -66,7 +64,7 @@ public class SwipeableMessageAdapter
private final View.OnClickListener swipeableViewContainerOnClickListener;
private Label label;
private int selectedPosition;
private int selectedPosition = -1;
private boolean activateOnItemClick;
public void setActivateOnItemClick(boolean activateOnItemClick) {

View File

@ -303,11 +303,10 @@ public class AndroidAddressRepository implements AddressRepository {
}
}
@NonNull
@Override
public BitmessageAddress getAddress(String address) {
List<BitmessageAddress> result = find("address = '" + address + "'");
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.UuidUtils;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
@ -154,29 +155,30 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
@Override
public int countUnread(Label label) {
String[] args;
String where;
if (label == null) {
return 0;
}
if (label == LABEL_ARCHIVE) {
where = "";
args = new String[]{
Label.Type.UNREAD.name()
};
return 0;
} else if (label == null) {
return (int) DatabaseUtils.queryNumEntries(
sql.getReadableDatabase(),
TABLE_NAME,
"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()
}
);
} else {
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=?) AND ";
args = new String[]{
String.valueOf(label.getId()),
Label.Type.UNREAD.name()
};
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()
}
);
}
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
);
}
@NonNull
@ -249,7 +251,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
}
@NonNull
protected List<Long> findIds(String where) {
private List<Long> findIds(String where) {
List<Long> result = new LinkedList<>();
// 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"
)) {
while (c.moveToNext()) {
long id = c.getLong(c.getColumnIndex(COLUMN_ID));
long id = c.getLong(0);
result.add(id);
}
}
@ -315,11 +317,21 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
builder.IV(InventoryVector.fromHash(iv));
String sender = c.getString(c.getColumnIndex(COLUMN_SENDER));
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));
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.sent(c.getLong(c.getColumnIndex(COLUMN_SENT)));
@ -405,7 +417,7 @@ public class AndroidMessageRepository extends AbstractMessageRepository {
}
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

View File

@ -16,11 +16,16 @@
package ch.dissem.apps.abit.repository;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.UUID;
import ch.dissem.apps.abit.util.Assets;
import ch.dissem.apps.abit.util.UuidUtils;
/**
* Handles database migration and provides access.
@ -63,12 +68,37 @@ public class SqlHelper extends SQLiteOpenHelper {
executeMigration(db, "V3.4__Add_label_outbox");
case 6:
executeMigration(db, "V4.0__Create_table_message_parent");
case 7:
setMissingConversationIds(db);
default:
// Nothing to do. Let's assume we won't upgrade from a version that's newer than
// 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) {
for (String statement : Assets.readSqlStatements(ctx, "db/migration/" + name + ".sql")) {
db.execSQL(statement);