From dd8ac629b2345b7bbc96f326acc66ff783b64a7e Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 11 Sep 2015 07:59:39 +0200 Subject: [PATCH] Show subscriptions (actually, all contacts) --- .../ch/dissem/apps/abit/ApplicationTest.java | 13 -- app/src/main/AndroidManifest.xml | 9 ++ .../apps/abit/AbstractItemListFragment.java | 137 ++++++++++++++++++ .../apps/abit/MessageDetailFragment.java | 1 + .../dissem/apps/abit/MessageListActivity.java | 78 ++++++++-- .../dissem/apps/abit/MessageListFragment.java | 119 +-------------- .../apps/abit/SubscriptionDetailActivity.java | 87 +++++++++++ .../apps/abit/SubscriptionDetailFragment.java | 121 ++++++++++++++++ .../apps/abit/SubscriptionListFragment.java | 113 +++++++++++++++ .../abit/listeners/ListSelectionListener.java | 29 ++++ .../layout-sw600dp/activity_message_list.xml | 5 +- .../main/res/layout/activity_message_list.xml | 5 +- .../layout/fragment_subscription_detail.xml | 80 ++++++++++ app/src/main/res/layout/subscription_row.xml | 76 ++++++++++ app/src/main/res/values-de/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + 16 files changed, 730 insertions(+), 149 deletions(-) delete mode 100644 app/src/androidTest/java/ch/dissem/apps/abit/ApplicationTest.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailActivity.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/listeners/ListSelectionListener.java create mode 100644 app/src/main/res/layout/fragment_subscription_detail.xml create mode 100644 app/src/main/res/layout/subscription_row.xml diff --git a/app/src/androidTest/java/ch/dissem/apps/abit/ApplicationTest.java b/app/src/androidTest/java/ch/dissem/apps/abit/ApplicationTest.java deleted file mode 100644 index bd2f7c5..0000000 --- a/app/src/androidTest/java/ch/dissem/apps/abit/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package ch.dissem.apps.abit; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce67b34..5cb1fb1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,15 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".MessageListActivity"/> + + + extends ListFragment { + /** + * The serialization (saved instance state) Bundle key representing the + * activated item position. Only used on tablets. + */ + private static final String STATE_ACTIVATED_POSITION = "activated_position"; + /** + * A dummy implementation of the {@link ListSelectionListener} interface that does + * nothing. Used only when this fragment is not attached to an activity. + */ + private static ListSelectionListener dummyCallbacks = new ListSelectionListener() { + @Override + public void onItemSelected(Object plaintext) { + } + }; + protected BitmessageContext bmc; + /** + * The fragment's current callback object, which is notified of list item + * clicks. + */ + private ListSelectionListener callbacks = dummyCallbacks; + /** + * The current activated item position. Only used on tablets. + */ + private int activatedPosition = ListView.INVALID_POSITION; + + abstract void updateList(Label label); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + bmc = Singleton.getBitmessageContext(getActivity()); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // Restore the previously serialized activated item position. + if (savedInstanceState != null + && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { + setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); + } + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + // Activities containing this fragment must implement its callbacks. + if (!(activity instanceof ListSelectionListener)) { + throw new IllegalStateException("Activity must implement fragment's callbacks."); + } + + callbacks = (ListSelectionListener) activity; + } + + @Override + public void onDetach() { + super.onDetach(); + + // Reset the active callbacks interface to the dummy implementation. + callbacks = dummyCallbacks; + } + + @Override + public void onListItemClick(ListView listView, View view, int position, long id) { + super.onListItemClick(listView, view, position, id); + + // Notify the active callbacks interface (the activity, if the + // fragment is attached to one) that an item has been selected. + callbacks.onItemSelected((T) listView.getItemAtPosition(position)); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (activatedPosition != ListView.INVALID_POSITION) { + // Serialize and persist the activated item position. + outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition); + } + } + + /** + * Turns on activate-on-click mode. When this mode is on, list items will be + * given the 'activated' state when touched. + */ + public void setActivateOnItemClick(boolean activateOnItemClick) { + // When setting CHOICE_MODE_SINGLE, ListView will automatically + // give items the 'activated' state when touched. + getListView().setChoiceMode(activateOnItemClick + ? ListView.CHOICE_MODE_SINGLE + : ListView.CHOICE_MODE_NONE); + } + + private void setActivatedPosition(int position) { + if (position == ListView.INVALID_POSITION) { + getListView().setItemChecked(activatedPosition, false); + } else { + getListView().setItemChecked(position, true); + } + + activatedPosition = position; + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java index dfe0d14..dbca865 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java +++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.java @@ -119,6 +119,7 @@ public class MessageDetailFragment extends Fragment { item.addLabels(bmc.messages().getLabels(Label.Type.TRASH)); bmc.messages().save(item); } + getActivity().onBackPressed(); return true; case R.id.mark_unread: item.addLabels(bmc.messages().getLabels(Label.Type.UNREAD)); diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java b/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java index 84c2dc0..71be3ab 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/MessageListActivity.java @@ -2,6 +2,7 @@ package ch.dissem.apps.abit; import android.content.Intent; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; @@ -9,10 +10,12 @@ import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import ch.dissem.apps.abit.listeners.ActionBarListener; +import ch.dissem.apps.abit.listeners.ListSelectionListener; import ch.dissem.apps.abit.service.Singleton; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.Streamable; import ch.dissem.bitmessage.entity.valueobject.Label; import com.mikepenz.community_material_typeface_library.CommunityMaterial; import com.mikepenz.google_material_typeface_library.GoogleMaterial; @@ -31,6 +34,7 @@ import com.mikepenz.materialdrawer.model.interfaces.Nameable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Serializable; import java.util.ArrayList; @@ -47,12 +51,12 @@ import java.util.ArrayList; * (if present) is a {@link MessageDetailFragment}. *

* This activity also implements the required - * {@link MessageListFragment.Callbacks} interface + * {@link ListSelectionListener} interface * to listen for item selections. *

*/ public class MessageListActivity extends AppCompatActivity - implements MessageListFragment.Callbacks, ActionBarListener { + implements ListSelectionListener, ActionBarListener { public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"; public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"; @@ -81,6 +85,9 @@ public class MessageListActivity extends AppCompatActivity final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); + MessageListFragment listFragment = new MessageListFragment(); + getSupportFragmentManager().beginTransaction().replace(R.id.item_list, listFragment).commit(); + if (findViewById(R.id.message_detail_container) != null) { // The detail container view will be present only in the // large-screen layouts (res/values-large and @@ -90,9 +97,7 @@ public class MessageListActivity extends AppCompatActivity // In two-pane mode, list items should be given the // 'activated' state when touched. - ((MessageListFragment) getSupportFragmentManager() - .findFragmentById(R.id.message_list)) - .setActivateOnItemClick(true); + listFragment.setActivateOnItemClick(true); } createDrawer(toolbar); @@ -105,6 +110,20 @@ public class MessageListActivity extends AppCompatActivity } } + private void changeList(AbstractItemListFragment listFragment) { + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.item_list, listFragment) + .addToBackStack(null) + .commit(); + + if (twoPane) { + // In two-pane mode, list items should be given the + // 'activated' state when touched. + listFragment.setActivateOnItemClick(true); + } + } + private void createDrawer(Toolbar toolbar) { final ArrayList profiles = new ArrayList<>(); for (BitmessageAddress identity : bmc.addresses().getIdentities()) { @@ -191,7 +210,7 @@ public class MessageListActivity extends AppCompatActivity .withDrawerItems(drawerItems) .addStickyDrawerItems( new SecondaryDrawerItem() - .withName(getString(R.string.subscriptions)) + .withName(R.string.subscriptions) .withIcon(CommunityMaterial.Icon.cmd_rss_box), new SecondaryDrawerItem() .withName(R.string.settings) @@ -202,14 +221,25 @@ public class MessageListActivity extends AppCompatActivity public boolean onItemClick(AdapterView adapterView, View view, int i, long l, IDrawerItem item) { if (item.getTag() instanceof Label) { selectedLabel = (Label) item.getTag(); - ((MessageListFragment) getSupportFragmentManager() - .findFragmentById(R.id.message_list)).updateList(selectedLabel); - return true; + if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof MessageListFragment)) { + MessageListFragment listFragment = new MessageListFragment(); + changeList(listFragment); + listFragment.updateList(selectedLabel); + } else { + ((MessageListFragment) getSupportFragmentManager() + .findFragmentById(R.id.item_list)).updateList(selectedLabel); + } + return false; } else if (item instanceof Nameable) { Nameable ni = (Nameable) item; switch (ni.getNameRes()) { case R.string.subscriptions: - // TODO + if (!(getSupportFragmentManager().findFragmentById(R.id.item_list) instanceof SubscriptionListFragment)) { + changeList(new SubscriptionListFragment()); + } else { + ((SubscriptionListFragment) getSupportFragmentManager() + .findFragmentById(R.id.item_list)).updateList(); + } break; case R.string.settings: startActivity(new Intent(MessageListActivity.this, SettingsActivity.class)); @@ -219,6 +249,7 @@ public class MessageListActivity extends AppCompatActivity return false; } }) + .withCloseOnClick(true) .build(); } @@ -253,18 +284,25 @@ public class MessageListActivity extends AppCompatActivity } /** - * Callback method from {@link MessageListFragment.Callbacks} + * Callback method from {@link ListSelectionListener} * indicating that the item with the given ID was selected. */ @Override - public void onItemSelected(Plaintext plaintext) { + public void onItemSelected(Serializable item) { if (twoPane) { // In two-pane mode, show the detail view in this activity by // adding or replacing the detail fragment using a // fragment transaction. Bundle arguments = new Bundle(); - arguments.putSerializable(MessageDetailFragment.ARG_ITEM, plaintext); - MessageDetailFragment fragment = new MessageDetailFragment(); + arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item); + Fragment fragment; + if (item instanceof Plaintext) + fragment = new MessageDetailFragment(); + else if (item instanceof BitmessageAddress) + fragment = new SubscriptionDetailFragment(); + else + throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " + + item.getClass().getSimpleName()); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction() .replace(R.id.message_detail_container, fragment) @@ -272,8 +310,16 @@ public class MessageListActivity extends AppCompatActivity } else { // In single-pane mode, simply start the detail activity // for the selected item ID. - Intent detailIntent = new Intent(this, MessageDetailActivity.class); - detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, plaintext); + Intent detailIntent; + if (item instanceof Plaintext) + detailIntent = new Intent(this, MessageDetailActivity.class); + else if (item instanceof BitmessageAddress) + detailIntent = new Intent(this, SubscriptionDetailActivity.class); + else + throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but was " + + item.getClass().getSimpleName()); + + detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item); startActivity(detailIntent); } } diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java index 45ef52b..cb02e54 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java +++ b/app/src/main/java/ch/dissem/apps/abit/MessageListFragment.java @@ -12,8 +12,10 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import ch.dissem.apps.abit.listeners.ActionBarListener; +import ch.dissem.apps.abit.listeners.ListSelectionListener; import ch.dissem.apps.abit.service.Singleton; import ch.dissem.bitmessage.BitmessageContext; +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; @@ -24,55 +26,14 @@ import ch.dissem.bitmessage.ports.MessageRepository; * 'activated' state upon selection. This helps indicate which item is * currently being viewed in a {@link MessageDetailFragment}. *

- * Activities containing this fragment MUST implement the {@link Callbacks} + * Activities containing this fragment MUST implement the {@link ListSelectionListener} * interface. */ -public class MessageListFragment extends ListFragment { - - /** - * The serialization (saved instance state) Bundle key representing the - * activated item position. Only used on tablets. - */ - private static final String STATE_ACTIVATED_POSITION = "activated_position"; - - /** - * The fragment's current callback object, which is notified of list item - * clicks. - */ - private Callbacks callbacks = dummyCallbacks; - - /** - * The current activated item position. Only used on tablets. - */ - private int activatedPosition = ListView.INVALID_POSITION; +public class MessageListFragment extends AbstractItemListFragment

{ private Label currentLabel; private MenuItem emptyTrashMenuItem; - private BitmessageContext bmc; - - /** - * A callback interface that all activities containing this fragment must - * implement. This mechanism allows activities to be notified of item - * selections. - */ - public interface Callbacks { - /** - * Callback for when an item has been selected. - */ - void onItemSelected(Plaintext plaintext); - } - - /** - * A dummy implementation of the {@link Callbacks} interface that does - * nothing. Used only when this fragment is not attached to an activity. - */ - private static Callbacks dummyCallbacks = new Callbacks() { - @Override - public void onItemSelected(Plaintext plaintext) { - } - }; - /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). @@ -84,7 +45,6 @@ public class MessageListFragment extends ListFragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - bmc = Singleton.getBitmessageContext(getActivity()); setHasOptionsMenu(true); } @@ -95,6 +55,7 @@ public class MessageListFragment extends ListFragment { updateList(((MessageListActivity) getActivity()).getSelectedLabel()); } + @Override public void updateList(Label label) { currentLabel = label; setListAdapter(new ArrayAdapter<Plaintext>( @@ -149,37 +110,6 @@ public class MessageListFragment extends ListFragment { return rootView; } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - // Restore the previously serialized activated item position. - if (savedInstanceState != null - && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { - setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); - } - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - // Activities containing this fragment must implement its callbacks. - if (!(activity instanceof Callbacks)) { - throw new IllegalStateException("Activity must implement fragment's callbacks."); - } - - callbacks = (Callbacks) activity; - } - - @Override - public void onDetach() { - super.onDetach(); - - // Reset the active callbacks interface to the dummy implementation. - callbacks = dummyCallbacks; - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.message_list, menu); @@ -204,43 +134,4 @@ public class MessageListFragment extends ListFragment { } } - @Override - public void onListItemClick(ListView listView, View view, int position, long id) { - super.onListItemClick(listView, view, position, id); - - // Notify the active callbacks interface (the activity, if the - // fragment is attached to one) that an item has been selected. - callbacks.onItemSelected((Plaintext) listView.getItemAtPosition(position)); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - if (activatedPosition != ListView.INVALID_POSITION) { - // Serialize and persist the activated item position. - outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition); - } - } - - /** - * Turns on activate-on-click mode. When this mode is on, list items will be - * given the 'activated' state when touched. - */ - public void setActivateOnItemClick(boolean activateOnItemClick) { - // When setting CHOICE_MODE_SINGLE, ListView will automatically - // give items the 'activated' state when touched. - getListView().setChoiceMode(activateOnItemClick - ? ListView.CHOICE_MODE_SINGLE - : ListView.CHOICE_MODE_NONE); - } - - private void setActivatedPosition(int position) { - if (position == ListView.INVALID_POSITION) { - getListView().setItemChecked(activatedPosition, false); - } else { - getListView().setItemChecked(position, true); - } - - activatedPosition = position; - } } diff --git a/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailActivity.java b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailActivity.java new file mode 100644 index 0000000..57bf32b --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailActivity.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 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.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; + + +/** + * An activity representing a single Subscription detail screen. This + * activity is only used on handset devices. On tablet-size devices, + * item details are presented side-by-side with a list of items + * in a {@link MessageListActivity}. + * <p/> + * This activity is mostly just a 'shell' activity containing nothing + * more than a {@link SubscriptionDetailFragment}. + */ +public class SubscriptionDetailActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.toolbar_layout); + + final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + // Show the Up button in the action bar. + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + // savedInstanceState is non-null when there is fragment state + // saved from previous configurations of this activity + // (e.g. when rotating the screen from portrait to landscape). + // In this case, the fragment will automatically be re-added + // to its container so we don't need to manually add it. + // For more information, see the Fragments API guide at: + // + // http://developer.android.com/guide/components/fragments.html + // + if (savedInstanceState == null) { + // Create the detail fragment and add it to the activity + // using a fragment transaction. + Bundle arguments = new Bundle(); + arguments.putSerializable(SubscriptionDetailFragment.ARG_ITEM, + getIntent().getSerializableExtra(SubscriptionDetailFragment.ARG_ITEM)); + SubscriptionDetailFragment fragment = new SubscriptionDetailFragment(); + fragment.setArguments(arguments); + getSupportFragmentManager().beginTransaction() + .add(R.id.content, fragment) + .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, MessageListActivity.class)); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java new file mode 100644 index 0000000..fa16aec --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/SubscriptionDetailFragment.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015 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.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.Switch; +import android.widget.TextView; +import ch.dissem.apps.abit.service.Singleton; +import ch.dissem.bitmessage.entity.BitmessageAddress; + + +/** + * A fragment representing a single Message detail screen. + * This fragment is either contained in a {@link MessageListActivity} + * in two-pane mode (on tablets) or a {@link MessageDetailActivity} + * on handsets. + */ +public class SubscriptionDetailFragment extends Fragment { + /** + * The fragment argument representing the item ID that this fragment + * represents. + */ + public static final String ARG_ITEM = "item"; + + /** + * The content this fragment is presenting. + */ + private BitmessageAddress item; + + + /** + * Mandatory empty constructor for the fragment manager to instantiate the + * fragment (e.g. upon screen orientation changes). + */ + public SubscriptionDetailFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments().containsKey(ARG_ITEM)) { + // Load the dummy content specified by the fragment + // arguments. In a real-world scenario, use a Loader + // to load content from a content provider. + item = (BitmessageAddress) getArguments().getSerializable(ARG_ITEM); + } + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_subscription_detail, container, false); + + // Show the dummy content as text in a TextView. + if (item != null) { + ((ImageView) rootView.findViewById(R.id.avatar)).setImageDrawable(new Identicon(item)); + TextView name = (TextView) rootView.findViewById(R.id.name); + name.setText(item.toString()); + name.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Nothing to do + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Nothing to do + } + + @Override + public void afterTextChanged(Editable s) { + item.setAlias(s.toString()); + } + }); + TextView address = (TextView) rootView.findViewById(R.id.address); + address.setText(item.getAddress()); + address.setSelected(true); + ((TextView) rootView.findViewById(R.id.stream_number)).setText(getActivity().getString(R.string.stream_number, item.getStream())); + Switch active = (Switch) rootView.findViewById(R.id.active); + active.setChecked(item.isSubscribed()); + active.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + item.setSubscribed(isChecked); + } + }); + } + + return rootView; + } + + @Override + public void onPause() { + Singleton.getBitmessageContext(getActivity()).addresses().save(item); + super.onPause(); + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java b/app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java new file mode 100644 index 0000000..a3c7b00 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/SubscriptionListFragment.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015 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.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.valueobject.Label; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by chris on 06.09.15. + */ +public class SubscriptionListFragment extends AbstractItemListFragment<BitmessageAddress> { + @Override + public void onResume() { + super.onResume(); + + updateList(); + } + + public void updateList() { + List<BitmessageAddress> addresses = bmc.addresses().getContacts(); + Collections.sort(addresses, new Comparator<BitmessageAddress>() { + /** + * Yields the following order: + * <ol> + * <li>Subscribed addresses come first</li> + * <li>Addresses with Aliases (alphabetically)</li> + * <li>Addresses (alphabetically)</li> + * </ol> + */ + @Override + public int compare(BitmessageAddress lhs, BitmessageAddress rhs) { + if (lhs.isSubscribed() == rhs.isSubscribed()) { + if (lhs.getAlias() != null) { + if (rhs.getAlias() != null) { + return lhs.getAlias().compareTo(rhs.getAlias()); + } else { + return -1; + } + } else if (rhs.getAlias() != null) { + return 1; + } else { + return lhs.getAddress().compareTo(rhs.getAddress()); + } + } + if (lhs.isSubscribed()) { + return -1; + } else { + return 1; + } + } + }); + setListAdapter(new ArrayAdapter<BitmessageAddress>( + getActivity(), + android.R.layout.simple_list_item_activated_1, + android.R.id.text1, + addresses) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + convertView = inflater.inflate(R.layout.subscription_row, null, false); + } + BitmessageAddress item = getItem(position); + ((ImageView) convertView.findViewById(R.id.avatar)).setImageDrawable(new Identicon(item)); + TextView name = (TextView) convertView.findViewById(R.id.name); + name.setText(item.toString()); + TextView streamNumber = (TextView) convertView.findViewById(R.id.stream_number); + streamNumber.setText(getContext().getString(R.string.stream_number, item.getStream())); + convertView.findViewById(R.id.subscribed).setVisibility(item.isSubscribed() ? View.VISIBLE : View.INVISIBLE); + return convertView; + } + }); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_subscribtions, container, false); + + return rootView; + } + + @Override + void updateList(Label label) { + updateList(); + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/listeners/ListSelectionListener.java b/app/src/main/java/ch/dissem/apps/abit/listeners/ListSelectionListener.java new file mode 100644 index 0000000..6e050a3 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/listeners/ListSelectionListener.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 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.listeners; + +/** + * A callback interface that all activities containing this fragment must + * implement. This mechanism allows activities to be notified of item + * selections. + */ +public interface ListSelectionListener<T> { + /** + * Callback for when an item has been selected. + */ + void onItemSelected(T item); +} diff --git a/app/src/main/res/layout-sw600dp/activity_message_list.xml b/app/src/main/res/layout-sw600dp/activity_message_list.xml index 6a40fd9..d1b7b00 100644 --- a/app/src/main/res/layout-sw600dp/activity_message_list.xml +++ b/app/src/main/res/layout-sw600dp/activity_message_list.xml @@ -29,9 +29,8 @@ --> - <fragment - android:id="@+id/message_list" - android:name="ch.dissem.apps.abit.MessageListFragment" + <FrameLayout + android:id="@+id/item_list" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" diff --git a/app/src/main/res/layout/activity_message_list.xml b/app/src/main/res/layout/activity_message_list.xml index 0848bdc..bb2d440 100644 --- a/app/src/main/res/layout/activity_message_list.xml +++ b/app/src/main/res/layout/activity_message_list.xml @@ -13,11 +13,10 @@ app:popupTheme="@style/ThemeOverlay.AppCompat.Light" android:elevation="4dp" /> - <fragment xmlns:android="http://schemas.android.com/apk/res/android" + <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_below="@id/toolbar" - android:id="@+id/message_list" - android:name="ch.dissem.apps.abit.MessageListFragment" + android:id="@+id/item_list" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MessageListActivity" diff --git a/app/src/main/res/layout/fragment_subscription_detail.xml b/app/src/main/res/layout/fragment_subscription_detail.xml new file mode 100644 index 0000000..16c3c9b --- /dev/null +++ b/app/src/main/res/layout/fragment_subscription_detail.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2015 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. + --> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:id="@+id/avatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:src="@color/accent" + android:layout_margin="16dp"/> + + <EditText + android:id="@+id/name" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="Name" + android:layout_alignTop="@+id/avatar" + android:layout_toRightOf="@+id/avatar" + android:layout_toEndOf="@+id/avatar" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true"/> + + <TextView + android:id="@+id/address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Address" + android:lines="1" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_below="@+id/avatar" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:textStyle="bold" + /> + + <TextView + android:id="@+id/stream_number" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Stream #" + android:lines="1" + android:ellipsize="end" + android:textAppearance="?android:attr/textAppearanceSmall" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:layout_below="@+id/address"/> + + <Switch + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/enabled" + android:id="@+id/active" + android:paddingTop="16dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:layout_below="@+id/stream_number"/> + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/subscription_row.xml b/app/src/main/res/layout/subscription_row.xml new file mode 100644 index 0000000..58bbb6f --- /dev/null +++ b/app/src/main/res/layout/subscription_row.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2015 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. + --> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/avatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:src="@color/accent" + android:layout_margin="16dp"/> + + <TextView + android:id="@+id/name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Name" + android:lines="1" + android:ellipsize="end" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_alignTop="@+id/avatar" + android:layout_toRightOf="@+id/avatar" + android:layout_toEndOf="@+id/avatar" + android:paddingTop="0dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingBottom="0dp" + android:textStyle="bold" + /> + + <TextView + android:id="@+id/stream_number" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Stream #" + android:lines="1" + android:ellipsize="end" + android:textAppearance="?android:attr/textAppearanceSmall" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:layout_alignBottom="@+id/avatar" + android:layout_toRightOf="@+id/avatar" + android:layout_toEndOf="@+id/avatar"/> + + <com.mikepenz.iconics.view.IconicsImageView + android:id="@+id/subscribed" + android:layout_width="16dp" + android:layout_height="16dp" + app:iiv_color="@android:color/black" + app:iiv_icon="cmd-rss" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:layout_marginRight="16dp"/> + +</RelativeLayout> \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 552ee71..2222570 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -30,4 +30,7 @@ <string name="archive">Archiv</string> <string name="empty_trash">Papierkorb leeren</string> <string name="mark_unread">Als ungelesen markieren</string> + <string name="stream_number">Stream #%d</string> + <string name="enabled">Aktiv</string> + <string name="title_subscription_detail">Abonnement</string> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2034dac..43775b5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ <resources> <string name="app_name">Abit</string> <string name="title_message_detail">Message Detail</string> + <string name="title_subscription_detail">Subscription Detail</string> <string name="disable_sync">Disable Sync</string> <string name="enable_sync">Enable Sync</string> <string name="bitmessage_active">Bitmessage active</string> @@ -30,4 +31,6 @@ <string name="mark_unread">Mark unread</string> <string name="archive">Archive</string> <string name="empty_trash">Empty Trash</string> + <string name="stream_number">Stream #%d</string> + <string name="enabled">Enabled</string> </resources>