Fully migrated to Kotlin

This commit is contained in:
Christian Basler 2017-08-25 20:24:25 +02:00
parent 852e38b97d
commit cc18f34161
104 changed files with 5064 additions and 6111 deletions

View File

@ -1,156 +0,0 @@
/*
* 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.Context;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ListView;
import ch.dissem.apps.abit.listener.ListSelectionListener;
/**
* @author Christian Basler
*/
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.
*/
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 final ListSelectionListener<Object> dummyCallbacks =
new ListSelectionListener<Object>() {
@Override
public void onItemSelected(Object item) {
// NO OP
}
};
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
private ListSelectionListener<? super T> callbacks = dummyCallbacks;
/**
* The current activated item position. Only used on tablets.
*/
private int activatedPosition = ListView.INVALID_POSITION;
private boolean activateOnItemClick;
@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 onResume() {
super.onResume();
// 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);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
// Activities containing this fragment must implement its callbacks.
if (context instanceof ListSelectionListener) {
//noinspection unchecked
callbacks = (ListSelectionListener) context;
} else {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
}
@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.
//noinspection unchecked
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) {
this.activateOnItemClick = activateOnItemClick;
if (isVisible()) {
// 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;
}
@Override
public L getCurrentLabel() {
return null;
}
@Override
public boolean showPreviousList() {
return false;
}
}

View File

@ -0,0 +1,151 @@
/*
* 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.Context
import android.os.Bundle
import android.support.v4.app.ListFragment
import android.view.View
import android.widget.ListView
import ch.dissem.apps.abit.listener.ListSelectionListener
/**
* @author Christian Basler
*/
abstract class AbstractItemListFragment<L, T> : ListFragment(), ListHolder<L> {
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
@Suppress("UNCHECKED_CAST")
private var callbacks: ListSelectionListener<T> = DummyCallback as ListSelectionListener<T>
/**
* The current activated item position. Only used on tablets.
*/
private var activatedPosition = ListView.INVALID_POSITION
private var activateOnItemClick: Boolean = false
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
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 fun onResume() {
super.onResume()
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
listView.choiceMode = if (activateOnItemClick)
ListView.CHOICE_MODE_SINGLE
else
ListView.CHOICE_MODE_NONE
}
override fun onAttach(context: Context?) {
super.onAttach(context)
// Activities containing this fragment must implement its callbacks.
if (context is ListSelectionListener<*>) {
@Suppress("UNCHECKED_CAST")
callbacks = context as ListSelectionListener<T>
} else {
throw IllegalStateException("Activity must implement fragment's callbacks.")
}
}
override fun onDetach() {
super.onDetach()
// Reset the active callbacks interface to the dummy implementation.
@Suppress("UNCHECKED_CAST")
callbacks = DummyCallback as ListSelectionListener<T>
}
override fun onListItemClick(listView: ListView, view: View?, position: Int, id: Long) {
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.
@Suppress("UNCHECKED_CAST")
(listView.getItemAtPosition(position) as? T)?.let {
callbacks.onItemSelected(it)
}
}
override fun onSaveInstanceState(outState: Bundle?) {
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.
*/
override fun setActivateOnItemClick(activateOnItemClick: Boolean) {
this.activateOnItemClick = activateOnItemClick
if (isVisible) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
listView.choiceMode = if (activateOnItemClick)
ListView.CHOICE_MODE_SINGLE
else
ListView.CHOICE_MODE_NONE
}
}
private fun setActivatedPosition(position: Int) {
if (position == ListView.INVALID_POSITION) {
listView.setItemChecked(activatedPosition, false)
} else {
listView.setItemChecked(position, true)
}
activatedPosition = position
}
override var currentLabel: L? = null
override fun showPreviousList() = false
/**
* A dummy implementation of the [ListSelectionListener] interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
internal object DummyCallback : ListSelectionListener<Any> {
override fun onItemSelected(item: Any) {
// NO OP
}
}
companion object {
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
internal const val STATE_ACTIVATED_POSITION = "activated_position"
}
}

View File

@ -14,25 +14,25 @@
* limitations under the License.
*/
package ch.dissem.apps.abit;
package ch.dissem.apps.abit
import android.os.Bundle;
import android.os.Bundle
/**
* 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 MainActivity}.
* <p/>
* in a [MainActivity].
*
*
* This activity is mostly just a 'shell' activity containing nothing
* more than a {@link AddressDetailFragment}.
* more than a [AddressDetailFragment].
*/
public class AddressDetailActivity extends DetailActivity {
class AddressDetailActivity : DetailActivity() {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
@ -42,18 +42,18 @@ public class AddressDetailActivity extends DetailActivity {
// 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();
val arguments = Bundle()
arguments.putSerializable(AddressDetailFragment.ARG_ITEM,
getIntent().getSerializableExtra(AddressDetailFragment.ARG_ITEM));
AddressDetailFragment fragment = new AddressDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
intent.getSerializableExtra(AddressDetailFragment.ARG_ITEM))
val fragment = AddressDetailFragment()
fragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.content, fragment)
.commit();
.commit()
}
}
}

View File

@ -1,253 +0,0 @@
/*
* 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.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
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 android.widget.Toast;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Drawables;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.wif.WifExporter;
/**
* A fragment representing a single Message detail screen.
* This fragment is either contained in a {@link MainActivity}
* in two-pane mode (on tablets) or a {@link MessageDetailActivity}
* on handsets.
*/
public class AddressDetailFragment extends Fragment {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM = "item";
public static final String EXPORT_POSTFIX = ".keys.dat";
/**
* The content this fragment is presenting.
*/
private BitmessageAddress item;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM)) {
item = (BitmessageAddress) getArguments().getSerializable(ARG_ITEM);
}
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.address, menu);
FragmentActivity activity = getActivity();
Drawables.addIcon(activity, menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail);
Drawables.addIcon(activity, menu, R.id.share, GoogleMaterial.Icon.gmd_share);
Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete);
Drawables.addIcon(activity, menu, R.id.export,
CommunityMaterial.Icon.cmd_export)
.setVisible(item != null && item.getPrivateKey() != null);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
final Activity ctx = getActivity();
switch (menuItem.getItemId()) {
case R.id.write_message: {
BitmessageAddress identity = Singleton.getIdentity(ctx);
if (identity == null) {
Toast.makeText(ctx, R.string.no_identity_warning, Toast.LENGTH_LONG).show();
} else {
Intent intent = new Intent(ctx, ComposeMessageActivity.class);
intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, identity);
intent.putExtra(ComposeMessageActivity.EXTRA_RECIPIENT, item);
startActivity(intent);
}
return true;
}
case R.id.delete: {
int warning;
if (item.getPrivateKey() != null)
warning = R.string.delete_identity_warning;
else
warning = R.string.delete_contact_warning;
new AlertDialog.Builder(ctx)
.setMessage(warning)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Singleton.getAddressRepository(ctx).remove(item);
MainActivity mainActivity = MainActivity.getInstance();
if (item.getPrivateKey() != null && mainActivity != null) {
mainActivity.removeIdentityEntry(item);
}
item = null;
ctx.onBackPressed();
}
})
.setNegativeButton(android.R.string.no, null)
.show();
return true;
}
case R.id.export: {
new AlertDialog.Builder(ctx)
.setMessage(R.string.confirm_export)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TITLE, item +
EXPORT_POSTFIX);
WifExporter exporter = new WifExporter(Singleton
.getBitmessageContext(ctx));
exporter.addIdentity(item);
shareIntent.putExtra(Intent.EXTRA_TEXT, exporter.toString
());
startActivity(Intent.createChooser(shareIntent, null));
}
})
.setNegativeButton(android.R.string.no, null)
.show();
return true;
}
case R.id.share: {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, item.getAddress());
startActivity(Intent.createChooser(shareIntent, null));
return true;
}
default:
return false;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_address_detail, container, false);
// Show the dummy content as text in a TextView.
if (item != null) {
FragmentActivity activity = getActivity();
if (item.isChan()) {
activity.setTitle(R.string.title_chan_detail);
} else if (item.getPrivateKey() != null) {
activity.setTitle(R.string.title_identity_detail);
} else if (item.isSubscribed()) {
activity.setTitle(R.string.title_subscription_detail);
} else {
activity.setTitle(R.string.title_contact_detail);
}
((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(
getString(R.string.stream_number, item.getStream()));
if (item.getPrivateKey() == null) {
Switch active = (Switch) rootView.findViewById(R.id.active);
active.setChecked(item.isSubscribed());
active.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton button, boolean checked) {
item.setSubscribed(checked);
}
});
ImageView pubkeyAvailableImg = (ImageView) rootView.findViewById(R.id
.pubkey_available);
if (item.getPubkey() == null) {
pubkeyAvailableImg.setAlpha(0.3f);
TextView pubkeyAvailableDesc = (TextView) rootView.findViewById(R.id
.pubkey_available_desc);
pubkeyAvailableDesc.setText(R.string.pubkey_not_available);
}
} else {
rootView.findViewById(R.id.active).setVisibility(View.GONE);
rootView.findViewById(R.id.pubkey_available).setVisibility(View.GONE);
rootView.findViewById(R.id.pubkey_available_desc).setVisibility(View.GONE);
}
// QR code
ImageView qrCode = (ImageView) rootView.findViewById(R.id.qr_code);
qrCode.setImageBitmap(Drawables.qrCode(item));
}
return rootView;
}
@Override
public void onPause() {
if (item != null) {
Singleton.getAddressRepository(getContext()).save(item);
MainActivity mainActivity = MainActivity.getInstance();
if (mainActivity != null && item.getPrivateKey() != null) {
mainActivity.updateIdentityEntry(item);
}
}
super.onPause();
}
}

View File

@ -0,0 +1,206 @@
/*
* 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.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.Editable
import android.text.TextWatcher
import android.view.*
import android.widget.Toast
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.Drawables
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.wif.WifExporter
import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import kotlinx.android.synthetic.main.fragment_address_detail.*
/**
* A fragment representing a single Message detail screen.
* This fragment is either contained in a [MainActivity]
* in two-pane mode (on tablets) or a [MessageDetailActivity]
* on handsets.
*/
class AddressDetailFragment : Fragment() {
/**
* The content this fragment is presenting.
*/
private var item: BitmessageAddress? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments.containsKey(ARG_ITEM)) {
item = arguments.getSerializable(ARG_ITEM) as BitmessageAddress
}
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.address, menu)
val activity = activity
Drawables.addIcon(activity, menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail)
Drawables.addIcon(activity, menu, R.id.share, GoogleMaterial.Icon.gmd_share)
Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete)
Drawables.addIcon(activity, menu, R.id.export,
CommunityMaterial.Icon.cmd_export).isVisible = item != null && item!!.privateKey != null
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
val item = item ?: return false
val ctx = activity
when (menuItem.itemId) {
R.id.write_message -> {
val identity = Singleton.getIdentity(ctx)
if (identity == null) {
Toast.makeText(ctx, R.string.no_identity_warning, Toast.LENGTH_LONG).show()
} else {
val intent = Intent(ctx, ComposeMessageActivity::class.java)
intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, identity)
intent.putExtra(ComposeMessageActivity.EXTRA_RECIPIENT, item)
startActivity(intent)
}
return true
}
R.id.delete -> {
val warning = if (item.privateKey != null)
R.string.delete_identity_warning
else
R.string.delete_contact_warning
AlertDialog.Builder(ctx)
.setMessage(warning)
.setPositiveButton(android.R.string.yes) { _, _ ->
Singleton.getAddressRepository(ctx).remove(item)
val mainActivity = MainActivity.getInstance()
if (item.privateKey != null && mainActivity != null) {
mainActivity.removeIdentityEntry(item)
}
this.item = null
ctx.onBackPressed()
}
.setNegativeButton(android.R.string.no, null)
.show()
return true
}
R.id.export -> {
AlertDialog.Builder(ctx)
.setMessage(R.string.confirm_export)
.setPositiveButton(android.R.string.yes) { _, _ ->
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TITLE, item.toString() + EXPORT_POSTFIX)
val exporter = WifExporter(Singleton
.getBitmessageContext(ctx))
exporter.addIdentity(item)
shareIntent.putExtra(Intent.EXTRA_TEXT, exporter.toString())
startActivity(Intent.createChooser(shareIntent, null))
}
.setNegativeButton(android.R.string.no, null)
.show()
return true
}
R.id.share -> {
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain"
shareIntent.putExtra(Intent.EXTRA_TEXT, item.address)
startActivity(Intent.createChooser(shareIntent, null))
return true
}
else -> return false
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
= inflater.inflate(R.layout.fragment_address_detail, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Show the dummy content as text in a TextView.
item?.let { item ->
val activity = activity
when {
item.isChan -> activity.setTitle(R.string.title_chan_detail)
item.privateKey != null -> activity.setTitle(R.string.title_identity_detail)
item.isSubscribed -> activity.setTitle(R.string.title_subscription_detail)
else -> activity.setTitle(R.string.title_contact_detail)
}
avatar.setImageDrawable(Identicon(item))
name.setText(item.toString())
name.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// Nothing to do
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// Nothing to do
}
override fun afterTextChanged(s: Editable) {
item.alias = s.toString()
}
})
address.text = item.address
address.isSelected = true
stream_number.text = getString(R.string.stream_number, item.stream)
if (item.privateKey == null) {
active.isChecked = item.isSubscribed
active.setOnCheckedChangeListener { _, checked -> item.isSubscribed = checked }
if (item.pubkey == null) {
pubkey_available.alpha = 0.3f
pubkey_available_desc.setText(R.string.pubkey_not_available)
}
} else {
active.visibility = View.GONE
pubkey_available.visibility = View.GONE
pubkey_available_desc.visibility = View.GONE
}
// QR code
qr_code.setImageBitmap(Drawables.qrCode(item))
}
}
override fun onPause() {
item?.let { item ->
Singleton.getAddressRepository(context).save(item)
val mainActivity = MainActivity.getInstance()
if (mainActivity != null && item.privateKey != null) {
mainActivity.updateIdentityEntry(item)
}
}
super.onPause()
}
companion object {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
val ARG_ITEM = "item"
val EXPORT_POSTFIX = ".keys.dat"
}
}

View File

@ -1,176 +0,0 @@
/*
* 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.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
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 com.google.zxing.integration.android.IntentIntegrator;
import java.util.LinkedList;
import java.util.List;
import ch.dissem.apps.abit.repository.AndroidAddressRepository;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.FabUtils;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu;
/**
* Fragment that shows a list of all contacts, the ones we subscribed to first.
*/
public class AddressListFragment extends AbstractItemListFragment<Void, BitmessageAddress> {
private ArrayAdapter<BitmessageAddress> adapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
adapter = new ArrayAdapter<BitmessageAddress>(
getActivity(),
R.layout.subscription_row,
R.id.name,
new LinkedList<BitmessageAddress>()) {
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder v;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.subscription_row, parent, false);
v = new ViewHolder();
v.ctx = getContext();
v.avatar = (ImageView) convertView.findViewById(R.id.avatar);
v.name = (TextView) convertView.findViewById(R.id.name);
v.streamNumber = (TextView) convertView.findViewById(R.id.stream_number);
v.subscribed = convertView.findViewById(R.id.subscribed);
convertView.setTag(v);
} else {
v = (ViewHolder) convertView.getTag();
}
BitmessageAddress item = getItem(position);
assert item != null;
v.avatar.setImageDrawable(new Identicon(item));
v.name.setText(item.toString());
v.streamNumber.setText(v.ctx.getString(R.string.stream_number, item.getStream()));
v.subscribed.setVisibility(item.isSubscribed() ? View.VISIBLE : View.INVISIBLE);
return convertView;
}
};
setListAdapter(adapter);
}
@Override
public void onResume() {
super.onResume();
initFab((MainActivity) getActivity());
updateList();
}
public void updateList() {
adapter.clear();
final AndroidAddressRepository addressRepo = Singleton.getAddressRepository(getContext());
new AsyncTask<Void, BitmessageAddress, Void>() {
@Override
protected Void doInBackground(Void... params) {
List<String> ids = addressRepo.getContactIds();
for (String id : ids) {
BitmessageAddress address = addressRepo.getById(id);
publishProgress(address);
}
return null;
}
@Override
protected void onProgressUpdate(BitmessageAddress... values) {
for (BitmessageAddress address : values) {
adapter.add(address);
}
}
}.execute();
}
private void initFab(MainActivity activity){
activity.updateTitle(getString(R.string.contacts_and_subscriptions));
FabSpeedDialMenu menu = new FabSpeedDialMenu(activity);
menu.add(R.string.scan_qr_code).setIcon(R.drawable.ic_action_qr_code);
menu.add(R.string.create_contact).setIcon(R.drawable.ic_action_create_contact);
FabUtils.initFab(activity, R.drawable.ic_action_add_contact, menu)
.addOnMenuItemClickListener(new FabSpeedDial.OnMenuItemClickListener() {
@Override
public void onMenuItemClick(FloatingActionButton floatingActionButton, @Nullable TextView textView, int itemId) {
switch (itemId) {
case 1:
IntentIntegrator.forSupportFragment(AddressListFragment.this)
.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.initiateScan();
break;
case 2:
Intent intent = new Intent(getActivity(), CreateAddressActivity.class);
startActivity(intent);
break;
default:
break;
}
}
});
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
return inflater.inflate(R.layout.fragment_address_list, container, false);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null && data.hasExtra("SCAN_RESULT")) {
Uri uri = Uri.parse(data.getStringExtra("SCAN_RESULT"));
Intent intent = new Intent(getActivity(), CreateAddressActivity.class);
intent.setData(uri);
startActivity(intent);
}
}
@Override
public void updateList(Void label) {
updateList();
}
private static class ViewHolder {
private Context ctx;
private ImageView avatar;
private TextView name;
private TextView streamNumber;
private View subscribed;
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
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.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.FabUtils
import ch.dissem.bitmessage.entity.BitmessageAddress
import com.google.zxing.integration.android.IntentIntegrator
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.util.*
/**
* Fragment that shows a list of all contacts, the ones we subscribed to first.
*/
class AddressListFragment : AbstractItemListFragment<Void, BitmessageAddress>() {
private lateinit var adapter: ArrayAdapter<BitmessageAddress>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = object : ArrayAdapter<BitmessageAddress>(
activity,
R.layout.subscription_row,
R.id.name,
LinkedList()) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val result: View
val v: ViewHolder
if (convertView == null) {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.subscription_row, parent, false)
v = ViewHolder(
ctx = context,
avatar = view.findViewById(R.id.avatar) as ImageView,
name = view.findViewById(R.id.name) as TextView,
streamNumber = view.findViewById(R.id.stream_number) as TextView,
subscribed = view.findViewById(R.id.subscribed)
)
view.tag = v
result = view
} else {
v = convertView.tag as ViewHolder
result = convertView
}
val item = getItem(position)!!
v.avatar.setImageDrawable(Identicon(item))
v.name.text = item.toString()
v.streamNumber.text = v.ctx.getString(R.string.stream_number, item.stream)
v.subscribed.visibility = if (item.isSubscribed) View.VISIBLE else View.INVISIBLE
return result
}
}
listAdapter = adapter
}
override fun onResume() {
super.onResume()
initFab(activity as MainActivity)
updateList()
}
fun updateList() {
adapter.clear()
val addressRepo = Singleton.getAddressRepository(context)
doAsync {
addressRepo.getContactIds()
.map { addressRepo.getAddress(it) }
.forEach { address -> uiThread { adapter.add(address) } }
}
}
private fun initFab(activity: MainActivity) {
activity.updateTitle(getString(R.string.contacts_and_subscriptions))
val menu = FabSpeedDialMenu(activity)
menu.add(R.string.scan_qr_code).setIcon(R.drawable.ic_action_qr_code)
menu.add(R.string.create_contact).setIcon(R.drawable.ic_action_create_contact)
FabUtils.initFab(activity, R.drawable.ic_action_add_contact, menu)
.addOnMenuItemClickListener { _, _, itemId ->
when (itemId) {
1 -> IntentIntegrator.forSupportFragment(this@AddressListFragment)
.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.initiateScan()
2 -> {
val intent = Intent(getActivity(), CreateAddressActivity::class.java)
startActivity(intent)
}
else -> {
}
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_address_list, container, false)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (data != null && data.hasExtra("SCAN_RESULT")) {
val uri = Uri.parse(data.getStringExtra("SCAN_RESULT"))
val intent = Intent(activity, CreateAddressActivity::class.java)
intent.data = uri
startActivity(intent)
}
}
override fun updateList(label: Void) {
updateList()
}
private data class ViewHolder(
val ctx: Context,
val avatar: ImageView,
val name: TextView,
val streamNumber: TextView,
val subscribed: View
)
}

View File

@ -1,105 +0,0 @@
/*
* 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.app.Activity;
import android.content.Context;
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 ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import static ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED;
/**
* Compose a new message.
*/
public class ComposeMessageActivity extends AppCompatActivity {
public static final String EXTRA_IDENTITY = "ch.dissem.abit.Message.SENDER";
public static final String EXTRA_RECIPIENT = "ch.dissem.abit.Message.RECIPIENT";
public static final String EXTRA_SUBJECT = "ch.dissem.abit.Message.SUBJECT";
public static final String EXTRA_CONTENT = "ch.dissem.abit.Message.CONTENT";
public static final String EXTRA_BROADCAST = "ch.dissem.abit.Message.IS_BROADCAST";
public static final String EXTRA_ENCODING = "ch.dissem.abit.Message.ENCODING";
public static final String EXTRA_PARENT = "ch.dissem.abit.Message.PARENT";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.toolbar_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//noinspection ConstantConditions
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_action_close);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(false);
// Display the fragment as the main content.
ComposeMessageFragment fragment = new ComposeMessageFragment();
fragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.replace(R.id.content, fragment)
.commit();
}
public static void launchReplyTo(Fragment fragment, Plaintext item) {
fragment.startActivity(getReplyIntent(fragment.getActivity(), item));
}
public static void launchReplyTo(Activity activity, Plaintext item) {
activity.startActivity(getReplyIntent(activity, item));
}
private static Intent getReplyIntent(Context ctx, Plaintext item) {
Intent replyIntent = new Intent(ctx, ComposeMessageActivity.class);
BitmessageAddress receivingIdentity = item.getTo();
if (receivingIdentity.isChan()) {
// reply to chan, not to the sender of the message
replyIntent.putExtra(EXTRA_RECIPIENT, receivingIdentity);
// I hate when people send as chan, so it won't be the default behaviour.
replyIntent.putExtra(EXTRA_IDENTITY, Singleton.getIdentity(ctx));
} else {
replyIntent.putExtra(EXTRA_RECIPIENT, item.getFrom());
replyIntent.putExtra(EXTRA_IDENTITY, receivingIdentity);
}
// if the original message was sent using extended encoding, use it as well
// so features like threading can be supported
if (item.getEncoding() == EXTENDED) {
replyIntent.putExtra(EXTRA_ENCODING, EXTENDED);
}
replyIntent.putExtra(EXTRA_PARENT, item);
String prefix;
if (item.getSubject().length() >= 3 && item.getSubject().substring(0, 3)
.equalsIgnoreCase("RE:")) {
prefix = "";
} else {
prefix = "RE: ";
}
replyIntent.putExtra(EXTRA_SUBJECT, prefix + item.getSubject());
replyIntent.putExtra(EXTRA_CONTENT,
"\n\n------------------------------------------------------\n"
+ item.getText());
return replyIntent;
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED
import kotlinx.android.synthetic.main.toolbar_layout.*
/**
* Compose a new message.
*/
class ComposeMessageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.toolbar_layout)
setSupportActionBar(toolbar)
supportActionBar!!.setHomeAsUpIndicator(R.drawable.ic_action_close)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setHomeButtonEnabled(false)
// Display the fragment as the main content.
val fragment = ComposeMessageFragment()
fragment.arguments = intent.extras
supportFragmentManager.beginTransaction()
.replace(R.id.content, fragment)
.commit()
}
companion object {
const val EXTRA_IDENTITY = "ch.dissem.abit.Message.SENDER"
const val EXTRA_RECIPIENT = "ch.dissem.abit.Message.RECIPIENT"
const val EXTRA_SUBJECT = "ch.dissem.abit.Message.SUBJECT"
const val EXTRA_CONTENT = "ch.dissem.abit.Message.CONTENT"
const val EXTRA_BROADCAST = "ch.dissem.abit.Message.IS_BROADCAST"
const val EXTRA_ENCODING = "ch.dissem.abit.Message.ENCODING"
const val EXTRA_PARENT = "ch.dissem.abit.Message.PARENT"
fun launchReplyTo(fragment: Fragment, item: Plaintext) {
fragment.startActivity(getReplyIntent(fragment.activity, item))
}
fun launchReplyTo(activity: Activity, item: Plaintext) {
activity.startActivity(getReplyIntent(activity, item))
}
private fun getReplyIntent(ctx: Context, item: Plaintext): Intent {
val replyIntent = Intent(ctx, ComposeMessageActivity::class.java)
val receivingIdentity = item.to
if (receivingIdentity!!.isChan) {
// reply to chan, not to the sender of the message
replyIntent.putExtra(EXTRA_RECIPIENT, receivingIdentity)
// I hate when people send as chan, so it won't be the default behaviour.
replyIntent.putExtra(EXTRA_IDENTITY, Singleton.getIdentity(ctx))
} else {
replyIntent.putExtra(EXTRA_RECIPIENT, item.from)
replyIntent.putExtra(EXTRA_IDENTITY, receivingIdentity)
}
// if the original message was sent using extended encoding, use it as well
// so features like threading can be supported
if (item.encoding == EXTENDED) {
replyIntent.putExtra(EXTRA_ENCODING, EXTENDED)
}
replyIntent.putExtra(EXTRA_PARENT, item)
val prefix: String
if (item.subject!!.length >= 3 && item.subject!!.substring(0, 3)
.equals("RE:", ignoreCase = true)) {
prefix = ""
} else {
prefix = "RE: "
}
replyIntent.putExtra(EXTRA_SUBJECT, prefix + item.subject!!)
replyIntent.putExtra(EXTRA_CONTENT,
"\n\n------------------------------------------------------\n" + item.text!!)
return replyIntent
}
}
}

View File

@ -1,266 +0,0 @@
/*
* 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.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.Toast;
import java.util.List;
import ch.dissem.apps.abit.adapter.ContactAdapter;
import ch.dissem.apps.abit.dialog.SelectEncodingDialogFragment;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Preferences;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
import static android.app.Activity.RESULT_OK;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_BROADCAST;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_CONTENT;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_ENCODING;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_PARENT;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_RECIPIENT;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_SUBJECT;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
/**
* Compose a new message.
*/
public class ComposeMessageFragment extends Fragment {
private BitmessageAddress identity;
private BitmessageAddress recipient;
private String subject;
private String content;
private AutoCompleteTextView recipientInput;
private EditText subjectInput;
private EditText bodyInput;
private boolean broadcast;
private Plaintext.Encoding encoding;
private Plaintext parent;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
if (getArguments().containsKey(EXTRA_IDENTITY)) {
identity = (BitmessageAddress) getArguments().getSerializable(EXTRA_IDENTITY);
if (getActivity() != null && (identity == null || identity.getPrivateKey() == null)) {
identity = Singleton.getIdentity(getActivity());
}
} else {
throw new IllegalStateException("No identity set for ComposeMessageFragment");
}
broadcast = getArguments().getBoolean(EXTRA_BROADCAST, false);
if (getArguments().containsKey(EXTRA_RECIPIENT)) {
recipient = (BitmessageAddress) getArguments().getSerializable(EXTRA_RECIPIENT);
}
if (getArguments().containsKey(EXTRA_SUBJECT)) {
subject = getArguments().getString(EXTRA_SUBJECT);
}
if (getArguments().containsKey(EXTRA_CONTENT)) {
content = getArguments().getString(EXTRA_CONTENT);
}
if (getArguments().containsKey(EXTRA_ENCODING)) {
encoding = (Plaintext.Encoding) getArguments().getSerializable(EXTRA_ENCODING);
} else {
encoding = Plaintext.Encoding.SIMPLE;
}
if (getArguments().containsKey(EXTRA_PARENT)) {
parent = (Plaintext) getArguments().getSerializable(EXTRA_PARENT);
}
} else {
throw new IllegalStateException("No identity set for ComposeMessageFragment");
}
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_compose_message, container, false);
recipientInput = (AutoCompleteTextView) rootView.findViewById(R.id.recipient);
if (broadcast) {
recipientInput.setVisibility(View.GONE);
} else {
final ContactAdapter adapter = new ContactAdapter(getContext());
recipientInput.setAdapter(adapter);
recipientInput.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
adapter.getItem(pos);
}
}
);
recipientInput.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long
id) {
recipient = adapter.getItem(position);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// leave current selection
}
});
if (recipient != null) {
recipientInput.setText(recipient.toString());
}
}
subjectInput = (EditText) rootView.findViewById(R.id.subject);
subjectInput.setText(subject);
bodyInput = (EditText) rootView.findViewById(R.id.body);
bodyInput.setText(content);
if (recipient == null) {
recipientInput.requestFocus();
} else if (subject == null || subject.isEmpty()) {
subjectInput.requestFocus();
} else {
bodyInput.requestFocus();
bodyInput.setSelection(0);
}
return rootView;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (identity == null || identity.getPrivateKey() == null) {
identity = Singleton.getIdentity(context);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.compose, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.send:
send();
return true;
case R.id.select_encoding:
SelectEncodingDialogFragment encodingDialog = new SelectEncodingDialogFragment();
Bundle args = new Bundle();
args.putSerializable(EXTRA_ENCODING, encoding);
encodingDialog.setArguments(args);
encodingDialog.setTargetFragment(this, 0);
encodingDialog.show(getFragmentManager(), "select encoding dialog");
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 0 && resultCode == RESULT_OK) {
encoding = (Plaintext.Encoding) data.getSerializableExtra(EXTRA_ENCODING);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void send() {
Plaintext.Builder builder;
BitmessageContext bmc = Singleton.getBitmessageContext(getContext());
if (broadcast) {
builder = new Plaintext.Builder(BROADCAST)
.from(identity);
} else {
String inputString = recipientInput.getText().toString();
if (recipient == null || !recipient.toString().equals(inputString)) {
try {
recipient = new BitmessageAddress(inputString);
} catch (Exception e) {
List<BitmessageAddress> contacts = Singleton.getAddressRepository
(getContext()).getContacts();
for (BitmessageAddress contact : contacts) {
if (inputString.equalsIgnoreCase(contact.getAlias())) {
recipient = contact;
if (inputString.equals(contact.getAlias()))
break;
}
}
}
}
if (recipient == null){
Toast.makeText(getContext(), R.string.error_msg_recipient_missing, Toast.LENGTH_LONG).show();
return;
}
builder = new Plaintext.Builder(MSG)
.from(identity)
.to(recipient);
}
if (!Preferences.requestAcknowledgements(getContext())){
builder.preventAck();
}
switch (encoding) {
case SIMPLE:
builder.message(
subjectInput.getText().toString(),
bodyInput.getText().toString()
);
break;
case EXTENDED:
builder.message(
new Message.Builder()
.subject(subjectInput.getText().toString())
.body(bodyInput.getText().toString())
.addParent(parent)
.build()
);
break;
default:
Toast.makeText(
getContext(),
getContext().getString(R.string.error_unsupported_encoding, encoding),
Toast.LENGTH_LONG
).show();
builder.message(
subjectInput.getText().toString(),
bodyInput.getText().toString()
);
break;
}
bmc.send(builder.build());
getActivity().finish();
}
}

View File

@ -0,0 +1,222 @@
/*
* 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.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.*
import android.widget.AdapterView
import android.widget.Toast
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_CONTENT
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_ENCODING
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_PARENT
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_RECIPIENT
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_SUBJECT
import ch.dissem.apps.abit.adapter.ContactAdapter
import ch.dissem.apps.abit.dialog.SelectEncodingDialogFragment
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
import ch.dissem.bitmessage.entity.valueobject.extended.Message
import kotlinx.android.synthetic.main.fragment_compose_message.*
/**
* Compose a new message.
*/
class ComposeMessageFragment : Fragment() {
private lateinit var identity: BitmessageAddress
private var recipient: BitmessageAddress? = null
private var subject: String = ""
private var content: String = ""
private var broadcast: Boolean = false
private var encoding: Plaintext.Encoding = Plaintext.Encoding.SIMPLE
private var parent: Plaintext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null) {
var id = arguments.getSerializable(EXTRA_IDENTITY) as? BitmessageAddress
if (context != null && (id == null || id.privateKey == null)) {
id = Singleton.getIdentity(context)
}
if (id?.privateKey != null) {
identity = id
} else {
throw IllegalStateException("No identity set for ComposeMessageFragment")
}
broadcast = arguments.getBoolean(EXTRA_BROADCAST, false)
if (arguments.containsKey(EXTRA_RECIPIENT)) {
recipient = arguments.getSerializable(EXTRA_RECIPIENT) as BitmessageAddress
}
if (arguments.containsKey(EXTRA_SUBJECT)) {
subject = arguments.getString(EXTRA_SUBJECT)
}
if (arguments.containsKey(EXTRA_CONTENT)) {
content = arguments.getString(EXTRA_CONTENT)
}
encoding = arguments.getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?: Plaintext.Encoding.SIMPLE
if (arguments.containsKey(EXTRA_PARENT)) {
parent = arguments.getSerializable(EXTRA_PARENT) as Plaintext
}
} else {
throw IllegalStateException("No identity set for ComposeMessageFragment")
}
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_compose_message, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (broadcast) {
recipient_input.visibility = View.GONE
} else {
val adapter = ContactAdapter(context)
recipient_input.setAdapter(adapter)
recipient_input.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) }
recipient_input.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
recipient = adapter.getItem(position)
}
override fun onNothingSelected(parent: AdapterView<*>) {
// leave current selection
}
}
if (recipient != null) {
recipient_input.setText(recipient.toString())
}
}
subject_input.setText(subject)
body_input.setText(content)
if (recipient == null) {
recipient_input.requestFocus()
} else if (subject.isEmpty()) {
subject_input.requestFocus()
} else {
body_input.requestFocus()
body_input.setSelection(0)
}
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater) {
inflater.inflate(R.menu.compose, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.send -> {
send()
return true
}
R.id.select_encoding -> {
val encodingDialog = SelectEncodingDialogFragment()
val args = Bundle()
args.putSerializable(EXTRA_ENCODING, encoding)
encodingDialog.arguments = args
encodingDialog.setTargetFragment(this, 0)
encodingDialog.show(fragmentManager, "select encoding dialog")
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == 0 && data != null && resultCode == RESULT_OK) {
encoding = data.getSerializableExtra(EXTRA_ENCODING) as Plaintext.Encoding
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun send() {
val builder: Plaintext.Builder
val bmc = Singleton.getBitmessageContext(context)
if (broadcast) {
builder = Plaintext.Builder(BROADCAST).from(identity)
} else {
val inputString = recipient_input.text.toString()
if (recipient == null || recipient?.toString() != inputString) {
try {
recipient = BitmessageAddress(inputString)
} catch (e: Exception) {
val contacts = Singleton.getAddressRepository(context).getContacts()
for (contact in contacts) {
if (inputString.equals(contact.alias, ignoreCase = true)) {
recipient = contact
if (inputString == contact.alias)
break
}
}
}
}
if (recipient == null) {
Toast.makeText(context, R.string.error_msg_recipient_missing, Toast.LENGTH_LONG).show()
return
}
builder = Plaintext.Builder(MSG)
.from(identity)
.to(recipient)
}
if (!Preferences.requestAcknowledgements(context)) {
builder.preventAck()
}
when (encoding) {
Plaintext.Encoding.SIMPLE -> builder.message(
subject_input.text.toString(),
body_input.text.toString()
)
Plaintext.Encoding.EXTENDED -> builder.message(
Message.Builder()
.subject(subject_input.text.toString())
.body(body_input.text.toString())
.addParent(parent)
.build()
)
else -> {
Toast.makeText(
context,
context.getString(R.string.error_unsupported_encoding, encoding),
Toast.LENGTH_LONG
).show()
builder.message(
subject_input.text.toString(),
body_input.text.toString()
)
}
}
bmc.send(builder.build())
activity.finish()
}
}

View File

@ -1,182 +0,0 @@
/*
* 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.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V2Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import static android.util.Base64.URL_SAFE;
public class CreateAddressActivity extends AppCompatActivity {
private static final Logger LOG = LoggerFactory.getLogger(CreateAddressActivity.class);
private static final Pattern KEY_VALUE_PATTERN = Pattern.compile("^([a-zA-Z]+)=(.*)$");
private byte[] pubkeyBytes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
if (uri != null)
setContentView(R.layout.activity_open_bitmessage_link);
else
setContentView(R.layout.activity_create_bitmessage_address);
final TextView address = (TextView) findViewById(R.id.address);
final EditText label = (EditText) findViewById(R.id.label);
final Switch subscribe = (Switch) findViewById(R.id.subscribe);
if (uri != null) {
String addressText = getAddress(uri);
String[] parameters = getParameters(uri);
for (String parameter : parameters) {
Matcher matcher = KEY_VALUE_PATTERN.matcher(parameter);
if (matcher.find()) {
String key = matcher.group(1).toLowerCase();
String value = matcher.group(2);
switch (key) {
case "label":
label.setText(value.trim());
break;
case "action":
subscribe.setChecked(value.trim().equalsIgnoreCase("subscribe"));
break;
case "pubkey":
pubkeyBytes = Base64.decode(value, URL_SAFE);
break;
default:
LOG.debug("Unknown attribute: " + key + "=" + value);
break;
}
}
}
address.setText(addressText);
}
final Button cancel = (Button) findViewById(R.id.cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setResult(Activity.RESULT_CANCELED);
finish();
}
});
findViewById(R.id.do_import).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onOK(address, label, subscribe);
}
});
}
private void onOK(TextView address, EditText label, Switch subscribe) {
String addressText = String.valueOf(address.getText()).trim();
try {
BitmessageAddress bmAddress = new BitmessageAddress(addressText);
bmAddress.setAlias(label.getText().toString());
BitmessageContext bmc = Singleton.getBitmessageContext
(CreateAddressActivity.this);
bmc.addContact(bmAddress);
if (subscribe.isChecked()) {
bmc.addSubscribtion(bmAddress);
}
if (pubkeyBytes != null) {
try {
final Pubkey pubkey;
InputStream pubkeyStream = new ByteArrayInputStream(pubkeyBytes);
long stream = bmAddress.getStream();
switch ((int) bmAddress.getVersion()) {
case 2:
pubkey = V2Pubkey.read(pubkeyStream, stream);
break;
case 3:
pubkey = V3Pubkey.read(pubkeyStream, stream);
break;
case 4:
pubkey = new V4Pubkey(V3Pubkey.read(pubkeyStream, stream));
break;
default:
pubkey = null;
break;
}
if (pubkey != null) {
bmAddress.setPubkey(pubkey);
}
} catch (Exception ignore) {
}
}
setResult(Activity.RESULT_OK);
finish();
} catch (RuntimeException e) {
address.setError(getString(R.string.error_illegal_address));
}
}
private String getAddress(Uri uri) {
StringBuilder result = new StringBuilder();
String schemeSpecificPart = uri.getSchemeSpecificPart();
if (!schemeSpecificPart.startsWith("BM-")) {
result.append("BM-");
}
if (schemeSpecificPart.contains("?")) {
result.append(schemeSpecificPart.substring(0, schemeSpecificPart.indexOf('?')));
} else if (schemeSpecificPart.contains("#")) {
result.append(schemeSpecificPart.substring(0, schemeSpecificPart.indexOf('#')));
} else {
result.append(schemeSpecificPart);
}
return result.toString();
}
private String[] getParameters(Uri uri) {
int index = uri.getSchemeSpecificPart().indexOf('?');
if (index >= 0) {
String parameterPart = uri.getSchemeSpecificPart().substring(index + 1);
return parameterPart.split("&");
} else {
return new String[0];
}
}
}

View File

@ -0,0 +1,146 @@
/*
* 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.app.Activity
import android.net.Uri
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Base64
import android.util.Base64.URL_SAFE
import android.widget.Button
import android.widget.EditText
import android.widget.Switch
import android.widget.TextView
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.payload.V2Pubkey
import ch.dissem.bitmessage.entity.payload.V3Pubkey
import ch.dissem.bitmessage.entity.payload.V4Pubkey
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.util.regex.Pattern
class CreateAddressActivity : AppCompatActivity() {
private var pubkeyBytes: ByteArray? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri = intent.data
if (uri != null)
setContentView(R.layout.activity_open_bitmessage_link)
else
setContentView(R.layout.activity_create_bitmessage_address)
val address = findViewById(R.id.address) as TextView
val label = findViewById(R.id.label) as EditText
val subscribe = findViewById(R.id.subscribe) as Switch
if (uri != null) {
val addressText = getAddress(uri)
val parameters = getParameters(uri)
for (parameter in parameters) {
val matcher = KEY_VALUE_PATTERN.matcher(parameter)
if (matcher.find()) {
val key = matcher.group(1).toLowerCase()
val value = matcher.group(2)
when (key) {
"label" -> label.setText(value.trim { it <= ' ' })
"action" -> subscribe.isChecked = value.trim { it <= ' ' }.equals("subscribe", ignoreCase = true)
"pubkey" -> pubkeyBytes = Base64.decode(value, URL_SAFE)
else -> LOG.debug("Unknown attribute: $key=$value")
}
}
}
address.text = addressText
}
val cancel = findViewById(R.id.cancel) as Button
cancel.setOnClickListener {
setResult(Activity.RESULT_CANCELED)
finish()
}
findViewById(R.id.do_import).setOnClickListener { onOK(address, label, subscribe) }
}
private fun onOK(address: TextView, label: EditText, subscribe: Switch) {
val addressText = address.text.toString().trim { it <= ' ' }
try {
val bmAddress = BitmessageAddress(addressText)
bmAddress.alias = label.text.toString()
val bmc = Singleton.getBitmessageContext(applicationContext)
bmc.addContact(bmAddress)
if (subscribe.isChecked) {
bmc.addSubscribtion(bmAddress)
}
if (pubkeyBytes != null) {
try {
val pubkeyStream = ByteArrayInputStream(pubkeyBytes)
val stream = bmAddress.stream
when (bmAddress.version.toInt()) {
2 -> V2Pubkey.read(pubkeyStream, stream)
3 -> V3Pubkey.read(pubkeyStream, stream)
4 -> V4Pubkey(V3Pubkey.read(pubkeyStream, stream))
else -> null
}?.let { bmAddress.pubkey = it }
} catch (ignore: Exception) {
}
}
setResult(Activity.RESULT_OK)
finish()
} catch (e: RuntimeException) {
address.error = getString(R.string.error_illegal_address)
}
}
private fun getAddress(uri: Uri): String {
val result = StringBuilder()
val schemeSpecificPart = uri.schemeSpecificPart
if (!schemeSpecificPart.startsWith("BM-")) {
result.append("BM-")
}
when {
schemeSpecificPart.contains("?") -> result.append(schemeSpecificPart.substring(0, schemeSpecificPart.indexOf('?')))
schemeSpecificPart.contains("#") -> result.append(schemeSpecificPart.substring(0, schemeSpecificPart.indexOf('#')))
else -> result.append(schemeSpecificPart)
}
return result.toString()
}
private fun getParameters(uri: Uri): Array<String> {
val index = uri.schemeSpecificPart.indexOf('?')
return if (index >= 0) {
uri.schemeSpecificPart
.substring(index + 1)
.split("&".toRegex())
.dropLastWhile { it.isEmpty() }
.toTypedArray()
} else {
emptyArray()
}
}
companion object {
private val LOG = LoggerFactory.getLogger(CreateAddressActivity::class.java)
private val KEY_VALUE_PATTERN = Pattern.compile("^([a-zA-Z]+)=(.*)$")
}
}

View File

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

View File

@ -0,0 +1,46 @@
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.view.MenuItem
import com.mikepenz.materialize.MaterializeBuilder
import kotlinx.android.synthetic.main.scrolling_toolbar_layout.*
/**
* @author Christian Basler
*/
abstract class DetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.scrolling_toolbar_layout)
setSupportActionBar(toolbar)
// Show the Up button in the action bar.
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
MaterializeBuilder()
.withActivity(this)
.withStatusBarColorRes(R.color.colorPrimaryDark)
.withTranslucentStatusBarProgrammatically(true)
.withStatusBarPadding(true)
.build()
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
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, Intent(this, MainActivity::class.java))
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@ -1,124 +0,0 @@
/*
* 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.graphics.*;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.text.TextPaint;
import ch.dissem.bitmessage.entity.BitmessageAddress;
/**
* @author Christian Basler
*/
public class Identicon extends Drawable {
private static final int SIZE = 9;
private static final int CENTER_COLUMN = 5;
private final Paint paint;
private final int color;
private final int background;
private final boolean[][] fields;
private final boolean chan;
private final TextPaint textPaint;
public Identicon(@NonNull BitmessageAddress input) {
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
textPaint = new TextPaint();
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setColor(0xFF607D8B);
textPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
chan = input.isChan();
byte[] hash = input.getRipe();
fields = new boolean[SIZE][SIZE];
color = Color.HSVToColor(new float[]{
Math.abs(hash[0] * hash[1] + hash[2]) % 360,
0.8f,
1.0f
});
background = Color.HSVToColor(new float[]{
Math.abs(hash[1] * hash[2] + hash[0]) % 360,
0.8f,
1.0f
});
for (int row = 0; row < SIZE; row++) {
if (!chan || row < 5 || row > 6) {
for (int column = 0; column <= CENTER_COLUMN; column++) {
if (
(row - SIZE / 2) * (row - SIZE / 2)
+ (column - SIZE / 2) * (column - SIZE / 2)
< SIZE / 2 * SIZE / 2
) {
fields[row][column] = hash[(row * CENTER_COLUMN + column) % hash.length]
>= 0;
fields[row][SIZE - column - 1] = fields[row][column];
}
}
}
}
}
@Override
public void draw(@NonNull Canvas canvas) {
float x, y;
float width = canvas.getWidth();
float height = canvas.getHeight();
float cellWidth = width / (float) SIZE;
float cellHeight = height / (float) SIZE;
paint.setColor(background);
canvas.drawCircle(width / 2, height / 2, width / 2, paint);
paint.setColor(color);
for (int row = 0; row < SIZE; row++) {
for (int column = 0; column < SIZE; column++) {
if (fields[row][column]) {
x = cellWidth * column;
y = cellHeight * row;
canvas.drawCircle(
x + cellWidth / 2, y + cellHeight / 2, cellHeight / 2,
paint
);
}
}
}
if (chan) {
textPaint.setTextSize(2 * cellHeight);
canvas.drawText("[chan]", width / 2, 6.7f * cellHeight, textPaint);
}
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSPARENT;
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.graphics.*
import android.graphics.drawable.Drawable
import android.text.TextPaint
import ch.dissem.bitmessage.entity.BitmessageAddress
/**
* @author Christian Basler
*/
class Identicon(input: BitmessageAddress) : Drawable() {
private val paint = Paint().apply {
style = Paint.Style.FILL
isAntiAlias = true
}
private val hash = input.ripe
private val isChan = input.isChan
private val fields = Array(SIZE) { BooleanArray(SIZE) }.apply {
for (row in 0 until SIZE) {
if (!isChan || row < 5 || row > 6) {
for (column in 0..CENTER_COLUMN) {
if ((row - SIZE / 2) * (row - SIZE / 2) + (column - SIZE / 2) * (column - SIZE / 2) < SIZE / 2 * SIZE / 2) {
this[row][column] = hash[(row * CENTER_COLUMN + column) % hash.size] >= 0
this[row][SIZE - column - 1] = this[row][column]
}
}
}
}
}
private val color = Color.HSVToColor(floatArrayOf((Math.abs(hash[0] * hash[1] + hash[2]) % 360).toFloat(), 0.8f, 1.0f))
private val background = Color.HSVToColor(floatArrayOf((Math.abs(hash[1] * hash[2] + hash[0]) % 360).toFloat(), 0.8f, 1.0f))
private val textPaint = TextPaint().apply {
textAlign = Paint.Align.CENTER
color = 0xFF607D8B.toInt()
typeface = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
}
override fun draw(canvas: Canvas) {
var x: Float
var y: Float
val width = canvas.width.toFloat()
val height = canvas.height.toFloat()
val cellWidth = width / SIZE.toFloat()
val cellHeight = height / SIZE.toFloat()
paint.color = background
canvas.drawCircle(width / 2, height / 2, width / 2, paint)
paint.color = color
for (row in 0 until SIZE) {
for (column in 0 until SIZE) {
if (fields[row][column]) {
x = cellWidth * column
y = cellHeight * row
canvas.drawCircle(
x + cellWidth / 2, y + cellHeight / 2, cellHeight / 2,
paint
)
}
}
}
if (isChan) {
textPaint.textSize = 2 * cellHeight
canvas.drawText("[isChan]", width / 2, 6.7f * cellHeight, textPaint)
}
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(cf: ColorFilter?) {
paint.colorFilter = cf
}
override fun getOpacity() = PixelFormat.TRANSPARENT
companion object {
private val SIZE = 9
private val CENTER_COLUMN = 5
}
}

View File

@ -1,83 +0,0 @@
/*
* 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.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator;
import java.io.IOException;
import ch.dissem.apps.abit.adapter.AddressSelectorAdapter;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.wif.WifImporter;
/**
* @author Christian Basler
*/
public class ImportIdentitiesFragment extends Fragment {
public static final String WIF_DATA = "wif_data";
private AddressSelectorAdapter adapter;
private WifImporter importer;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
String wifData = getArguments().getString(WIF_DATA);
BitmessageContext bmc = Singleton.getBitmessageContext(getActivity());
View view = inflater.inflate(R.layout.fragment_import_select_identities, container, false);
importer = new WifImporter(bmc, wifData);
adapter = new AddressSelectorAdapter(importer.getIdentities());
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(),
LinearLayoutManager.VERTICAL,
false);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(new SimpleListDividerDecorator(
ContextCompat.getDrawable(getActivity(), R.drawable.list_divider_h), true));
view.findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
importer.importAll(adapter.getSelected());
MainActivity mainActivity = MainActivity.getInstance();
if (mainActivity != null) {
for (BitmessageAddress selected : adapter.getSelected()) {
mainActivity.addIdentityEntry(selected);
}
}
getActivity().finish();
}
});
return view;
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.app.Fragment
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
import ch.dissem.apps.abit.adapter.AddressSelectorAdapter
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.bitmessage.wif.WifImporter
/**
* @author Christian Basler
*/
class ImportIdentitiesFragment : Fragment() {
private lateinit var adapter: AddressSelectorAdapter
private lateinit var importer: WifImporter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle): View =
inflater.inflate(R.layout.fragment_import_select_identities, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val wifData = arguments.getString(WIF_DATA)
val bmc = Singleton.getBitmessageContext(activity)
importer = WifImporter(bmc, wifData)
adapter = AddressSelectorAdapter(importer.getIdentities())
val layoutManager = LinearLayoutManager(activity,
LinearLayoutManager.VERTICAL,
false)
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
recyclerView.addItemDecoration(SimpleListDividerDecorator(
ContextCompat.getDrawable(activity, R.drawable.list_divider_h), true))
view.findViewById(R.id.finish).setOnClickListener {
importer.importAll(adapter.selected)
val mainActivity = MainActivity.getInstance()
if (mainActivity != null) {
for (selected in adapter.selected) {
mainActivity.addIdentityEntry(selected)
}
}
activity.finish()
}
}
companion object {
val WIF_DATA = "wif_data"
}
}

View File

@ -1,56 +0,0 @@
/*
* 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 static ch.dissem.apps.abit.ImportIdentitiesFragment.WIF_DATA;
/**
* @author Christian Basler
*/
public class ImportIdentityActivity extends DetailActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String wifData;
if (savedInstanceState == null) {
wifData = null;
} else {
wifData = savedInstanceState.getString(WIF_DATA);
}
if (wifData == null) {
getFragmentManager().beginTransaction()
.replace(R.id.content, new InputWifFragment())
.commit();
} else {
Bundle bundle = new Bundle();
bundle.putString(WIF_DATA, wifData);
ImportIdentitiesFragment fragment = new ImportIdentitiesFragment();
fragment.setArguments(bundle);
getFragmentManager().beginTransaction()
.replace(R.id.content, fragment)
.commit();
}
}
}

View File

@ -0,0 +1,48 @@
/*
* 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
/**
* @author Christian Basler
*/
class ImportIdentityActivity : DetailActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val wifData: String? = savedInstanceState?.getString(ImportIdentitiesFragment.WIF_DATA)
if (wifData == null) {
fragmentManager.beginTransaction()
.replace(R.id.content, InputWifFragment())
.commit()
} else {
val bundle = Bundle()
bundle.putString(ImportIdentitiesFragment.WIF_DATA, wifData)
val fragment = ImportIdentitiesFragment()
fragment.arguments = bundle
fragmentManager.beginTransaction()
.replace(R.id.content, fragment)
.commit()
}
}
}

View File

@ -1,122 +0,0 @@
/*
* 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.app.Fragment;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.github.angads25.filepicker.controller.DialogSelectionListener;
import com.github.angads25.filepicker.model.DialogConfigs;
import com.github.angads25.filepicker.model.DialogProperties;
import com.github.angads25.filepicker.view.FilePickerDialog;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import static ch.dissem.apps.abit.ImportIdentitiesFragment.WIF_DATA;
/**
* @author Christian Basler
*/
public class InputWifFragment extends Fragment {
private TextView wifData;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_import_input, container, false);
wifData = (TextView) view.findViewById(R.id.wif_input);
view.findViewById(R.id.next).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putString(WIF_DATA, wifData.getText().toString());
ImportIdentitiesFragment fragment = new ImportIdentitiesFragment();
fragment.setArguments(bundle);
getFragmentManager().beginTransaction()
.replace(R.id.content, fragment)
.commit();
}
});
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.import_input_data, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
DialogProperties properties = new DialogProperties();
properties.selection_mode = DialogConfigs.SINGLE_MODE;
properties.selection_type = DialogConfigs.FILE_SELECT;
properties.root = new File(DialogConfigs.DEFAULT_DIR);
properties.error_dir = new File(DialogConfigs.DEFAULT_DIR);
properties.extensions = null;
FilePickerDialog dialog = new FilePickerDialog(getActivity(), properties);
dialog.setTitle(getString(R.string.select_file_title));
dialog.setDialogSelectionListener(new DialogSelectionListener() {
@Override
public void onSelectedFilePaths(String[] files) {
if (files.length > 0) {
try (InputStream in = new FileInputStream(files[0])) {
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
//noinspection ConstantConditions
while ((length = in.read(buffer)) != -1) {
data.write(buffer, 0, length);
}
wifData.setText(data.toString("UTF-8"));
} catch (IOException e) {
Toast.makeText(
getActivity(),
R.string.error_loading_data,
Toast.LENGTH_SHORT
).show();
}
}
}
});
dialog.show();
return true;
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.app.Fragment
import android.os.Bundle
import android.view.*
import android.widget.Toast
import com.github.angads25.filepicker.model.DialogConfigs
import com.github.angads25.filepicker.model.DialogProperties
import com.github.angads25.filepicker.view.FilePickerDialog
import kotlinx.android.synthetic.main.fragment_import_input.*
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.IOException
/**
* @author Christian Basler
*/
class InputWifFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle): View =
inflater.inflate(R.layout.fragment_import_input, container, false)
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
next.setOnClickListener {
val bundle = Bundle()
bundle.putString(ImportIdentitiesFragment.WIF_DATA, wif_input.text.toString())
val fragment = ImportIdentitiesFragment().apply {
arguments = bundle
}
fragmentManager.beginTransaction()
.replace(R.id.content, fragment)
.commit()
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.import_input_data, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val properties = DialogProperties()
properties.selection_mode = DialogConfigs.SINGLE_MODE
properties.selection_type = DialogConfigs.FILE_SELECT
properties.root = File(DialogConfigs.DEFAULT_DIR)
properties.error_dir = File(DialogConfigs.DEFAULT_DIR)
properties.extensions = null
val dialog = FilePickerDialog(activity, properties)
dialog.setTitle(getString(R.string.select_file_title))
dialog.setDialogSelectionListener { files ->
if (files.isNotEmpty()) {
try {
FileInputStream(files[0]).use { inputStream ->
val data = ByteArrayOutputStream()
val buffer = ByteArray(1024)
var length: Int = inputStream.read(buffer)
while (length != -1) {
data.write(buffer, 0, length)
length = inputStream.read(buffer)
}
wif_input.setText(data.toByteArray().toString())
}
} catch (e: IOException) {
Toast.makeText(
activity,
R.string.error_loading_data,
Toast.LENGTH_SHORT
).show()
}
}
}
dialog.show()
return true
}
}

View File

@ -14,17 +14,17 @@
* limitations under the License.
*/
package ch.dissem.apps.abit;
package ch.dissem.apps.abit
/**
* @author Christian Basler
*/
public interface ListHolder<L> {
void updateList(L label);
interface ListHolder<L> {
fun updateList(label: L)
void setActivateOnItemClick(boolean activateOnItemClick);
fun setActivateOnItemClick(activateOnItemClick: Boolean)
L getCurrentLabel();
var currentLabel: L?
boolean showPreviousList();
fun showPreviousList(): Boolean
}

View File

@ -1,588 +0,0 @@
/*
* 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.content.Intent;
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;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.RelativeLayout;
import com.github.amlcurran.showcaseview.ShowcaseView;
import com.github.amlcurran.showcaseview.targets.Target;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.materialdrawer.AccountHeader;
import com.mikepenz.materialdrawer.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.interfaces.OnCheckedChangeListener;
import com.mikepenz.materialdrawer.model.DividerDrawerItem;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem;
import com.mikepenz.materialdrawer.model.SwitchDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import com.mikepenz.materialdrawer.model.interfaces.Nameable;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import ch.dissem.apps.abit.drawer.ProfileImageListener;
import ch.dissem.apps.abit.drawer.ProfileSelectionListener;
import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.apps.abit.service.BitmessageService;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.synchronization.SyncAdapter;
import ch.dissem.apps.abit.util.Labels;
import ch.dissem.apps.abit.util.NetworkUtils;
import ch.dissem.apps.abit.util.Preferences;
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 io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import static ch.dissem.apps.abit.ComposeMessageActivity.launchReplyTo;
import static ch.dissem.apps.abit.repository.AndroidMessageRepository.LABEL_ARCHIVE;
import static ch.dissem.apps.abit.service.BitmessageService.isRunning;
/**
* An activity representing a list of Messages. This activity
* has different presentations for handset and tablet-size devices. On
* handsets, the activity presents a list of items, which when touched,
* lead to a {@link MessageDetailActivity} representing
* item details. On tablets, the activity presents the list of items and
* item details side-by-side using two vertical panes.
* <p>
* The activity makes heavy use of fragments. The list of items is a
* {@link MessageListFragment} and the item details
* (if present) is a {@link MessageDetailFragment}.
* </p><p>
* This activity also implements the required
* {@link ListSelectionListener} interface
* to listen for item selections.
* </p>
*/
public class MainActivity extends AppCompatActivity
implements ListSelectionListener<Serializable> {
public static final String EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage";
public static final String EXTRA_SHOW_LABEL = "ch.dissem.abit.ShowLabel";
public static final String EXTRA_REPLY_TO_MESSAGE = "ch.dissem.abit.ReplyToMessage";
public static final String ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox";
public static final int ADD_IDENTITY = 1;
public static final int MANAGE_IDENTITY = 2;
private static final long ID_NODE_SWITCH = 1;
private static WeakReference<MainActivity> instance;
private boolean active;
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean twoPane;
private Label selectedLabel;
private BitmessageContext bmc;
private AccountHeader accountHeader;
private Drawer drawer;
private SwitchDrawerItem nodeSwitch;
private FabSpeedDial fab;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
instance = new WeakReference<>(this);
bmc = Singleton.getBitmessageContext(this);
setContentView(R.layout.activity_main);
fab = (FabSpeedDial) findViewById(R.id.fab);
fab.hide();
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
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
twoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
listFragment.setActivateOnItemClick(true);
}
createDrawer(toolbar);
// handle intents
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_SHOW_MESSAGE)) {
onItemSelected(intent.getSerializableExtra(EXTRA_SHOW_MESSAGE));
}
if (intent.hasExtra(EXTRA_REPLY_TO_MESSAGE)) {
Plaintext item = (Plaintext) intent.getSerializableExtra(EXTRA_REPLY_TO_MESSAGE);
launchReplyTo(this, item);
}
if (Preferences.useTrustedNode(this)) {
SyncAdapter.startSync(this);
} else {
SyncAdapter.stopSync(this);
}
if (drawer.isDrawerOpen()) {
RelativeLayout.LayoutParams lps = new RelativeLayout.LayoutParams(ViewGroup
.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
lps.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
int margin = ((Number) (getResources().getDisplayMetrics().density * 12)).intValue();
lps.setMargins(margin, margin, margin, margin);
new ShowcaseView.Builder(this)
.withMaterialShowcase()
.setStyle(R.style.CustomShowcaseTheme)
.setContentTitle(R.string.full_node)
.setContentText(R.string.full_node_description)
.setTarget(new Target() {
@Override
public Point getPoint() {
View view = drawer.getStickyFooter();
int[] location = new int[2];
view.getLocationInWindow(location);
int x = location[0] + 7 * view.getWidth() / 8;
int y = location[1] + view.getHeight() / 2;
return new Point(x, y);
}
})
.replaceEndButton(R.layout.showcase_button)
.hideOnTouchOutside()
.build()
.setButtonPosition(lps);
}
}
private <F extends Fragment & ListHolder> void changeList(F listFragment) {
if (active) {
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
// 'activated' state when touched.
listFragment.setActivateOnItemClick(true);
}
}
}
private void createDrawer(Toolbar toolbar) {
final ArrayList<IProfile> profiles = new ArrayList<>();
profiles.add(new ProfileSettingDrawerItem()
.withName(getString(R.string.add_identity))
.withDescription(getString(R.string.add_identity_summary))
.withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add)
.actionBar()
.paddingDp(5)
.colorRes(R.color.icons))
.withIdentifier(ADD_IDENTITY)
);
profiles.add(new ProfileSettingDrawerItem()
.withName(getString(R.string.manage_identity))
.withIcon(GoogleMaterial.Icon.gmd_settings)
.withIdentifier(MANAGE_IDENTITY)
);
// Create the AccountHeader
accountHeader = new AccountHeaderBuilder()
.withActivity(this)
.withHeaderBackground(R.drawable.header)
.withProfiles(profiles)
.withOnAccountHeaderProfileImageListener(new ProfileImageListener(this))
.withOnAccountHeaderListener(new ProfileSelectionListener(MainActivity.this, getSupportFragmentManager()))
.build();
if (profiles.size() > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles.get(0), true);
}
final ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
drawerItems.add(new PrimaryDrawerItem()
.withName(R.string.archive)
.withTag(LABEL_ARCHIVE)
.withIcon(CommunityMaterial.Icon.cmd_archive)
);
drawerItems.add(new DividerDrawerItem());
drawerItems.add(new PrimaryDrawerItem()
.withName(R.string.contacts_and_subscriptions)
.withIcon(GoogleMaterial.Icon.gmd_contacts));
drawerItems.add(new PrimaryDrawerItem()
.withName(R.string.settings)
.withIcon(GoogleMaterial.Icon.gmd_settings));
nodeSwitch = new SwitchDrawerItem()
.withIdentifier(ID_NODE_SWITCH)
.withName(R.string.full_node)
.withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
.withChecked(isRunning())
.withOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(IDrawerItem drawerItem, CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
NetworkUtils.enableNode(MainActivity.this);
} else {
NetworkUtils.disableNode(MainActivity.this);
}
}
});
drawer = new DrawerBuilder()
.withActivity(this)
.withToolbar(toolbar)
.withAccountHeader(accountHeader)
.withDrawerItems(drawerItems)
.addStickyDrawerItems(nodeSwitch)
.withOnDrawerItemClickListener(new DrawerItemClickListener())
.withShowDrawerOnFirstLaunch(true)
.build();
loadDrawerItemsAsynchronously();
}
private void loadDrawerItemsAsynchronously() {
new AsyncTask<Void, Void, List<BitmessageAddress>>() {
@Override
protected List<BitmessageAddress> doInBackground(Void... params) {
List<BitmessageAddress> identities = bmc.addresses().getIdentities();
if (identities.isEmpty()) {
// Create an initial identity
Singleton.getIdentity(MainActivity.this);
}
return identities;
}
@Override
protected void onPostExecute(List<BitmessageAddress> identities) {
for (BitmessageAddress identity : identities) {
addIdentityEntry(identity);
}
}
}.execute();
new AsyncTask<Void, Void, List<Label>>() {
@Override
protected List<Label> doInBackground(Void... params) {
return bmc.messages().getLabels();
}
@Override
protected void onPostExecute(List<Label> labels) {
if (getIntent().hasExtra(EXTRA_SHOW_LABEL)) {
selectedLabel = (Label) getIntent().getSerializableExtra(EXTRA_SHOW_LABEL);
} else if (selectedLabel == null) {
selectedLabel = labels.get(0);
}
for (Label label : labels) {
addLabelEntry(label);
}
IDrawerItem selectedDrawerItem = drawer.getDrawerItem(selectedLabel);
if (selectedDrawerItem != null) {
drawer.setSelection(selectedDrawerItem);
}
}
}.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 (itemList instanceof
MessageListFragment) {
((MessageListFragment) itemList).updateList(selectedLabel);
} else {
MessageListFragment listFragment = new MessageListFragment();
changeList(listFragment);
listFragment.updateList(selectedLabel);
}
return false;
} else if (item instanceof Nameable<?>) {
Nameable<?> ni = (Nameable<?>) item;
switch (ni.getName().getTextRes()) {
case R.string.contacts_and_subscriptions:
if (!(itemList instanceof AddressListFragment)) {
changeList(new AddressListFragment());
} else {
((AddressListFragment) itemList).updateList();
}
return false;
case R.string.settings:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.item_list, new SettingsFragment())
.addToBackStack(null)
.commit();
return false;
case R.string.full_node:
return true;
default:
return false;
}
}
return false;
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putSerializable("selectedLabel", selectedLabel);
}
@Override
@SuppressWarnings("unchecked")
protected void onRestoreInstanceState(Bundle savedInstanceState) {
selectedLabel = (Label) savedInstanceState.getSerializable("selectedLabel");
IDrawerItem selectedItem = drawer.getDrawerItem(selectedLabel);
if (selectedItem != null) {
drawer.setSelection(selectedItem);
}
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onResume() {
updateUnread();
if (Preferences.isFullNodeActive(this) && Preferences.isConnectionAllowed(MainActivity.this)) {
NetworkUtils.enableNode(this, false);
}
updateNodeSwitch();
Singleton.getMessageListener(this).resetNotification();
active = true;
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
active = false;
}
public void addIdentityEntry(BitmessageAddress identity) {
IProfile newProfile = new ProfileDrawerItem()
.withIcon(new Identicon(identity))
.withName(identity.toString())
.withNameShown(true)
.withEmail(identity.getAddress())
.withTag(identity);
if (accountHeader.getProfiles() != null) {
// we know that there are 2 setting elements.
// Set the new profile above them ;)
accountHeader.addProfile(
newProfile, accountHeader.getProfiles().size() - 2);
} else {
accountHeader.addProfiles(newProfile);
}
}
public void addLabelEntry(Label label) {
PrimaryDrawerItem item = new PrimaryDrawerItem()
.withName(label.toString())
.withTag(label)
.withIcon(Labels.getIcon(label))
.withIconColor(Labels.getColor(label));
drawer.addItemAtPosition(item, drawer.getDrawerItems().size() - 3);
}
public void updateIdentityEntry(BitmessageAddress identity) {
for (IProfile profile : accountHeader.getProfiles()) {
if (profile instanceof ProfileDrawerItem) {
ProfileDrawerItem profileDrawerItem = (ProfileDrawerItem) profile;
if (identity.equals(profileDrawerItem.getTag())) {
profileDrawerItem
.withName(identity.toString())
.withTag(identity);
return;
}
}
}
}
public void removeIdentityEntry(BitmessageAddress identity) {
for (IProfile profile : accountHeader.getProfiles()) {
if (profile instanceof ProfileDrawerItem) {
ProfileDrawerItem profileDrawerItem = (ProfileDrawerItem) profile;
if (identity.equals(profileDrawerItem.getTag())) {
accountHeader.removeProfile(profile);
return;
}
}
}
}
public void updateUnread() {
for (IDrawerItem item : drawer.getDrawerItems()) {
if (item.getTag() instanceof Label) {
Label label = (Label) item.getTag();
if (label != LABEL_ARCHIVE) {
int unread = bmc.messages().countUnread(label);
if (unread > 0) {
((PrimaryDrawerItem) item).withBadge(String.valueOf(unread));
} else {
((PrimaryDrawerItem) item).withBadge((String) null);
}
drawer.updateItem(item);
}
}
}
}
public static void updateNodeSwitch() {
final MainActivity i = getInstance();
if (i != null) {
i.runOnUiThread(new Runnable() {
@Override
public void run() {
i.nodeSwitch.withChecked(Preferences.isFullNodeActive(i));
i.drawer.updateStickyFooterItem(i.nodeSwitch);
}
});
}
}
/**
* Callback method from {@link ListSelectionListener}
* indicating that the item with the given ID was selected.
*/
@Override
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, item);
Fragment fragment;
if (item instanceof Plaintext) {
fragment = new MessageDetailFragment();
} else if (item instanceof BitmessageAddress) {
fragment = new AddressDetailFragment();
} 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)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent;
if (item instanceof Plaintext) {
detailIntent = new Intent(this, MessageDetailActivity.class);
detailIntent.putExtra(EXTRA_SHOW_LABEL, selectedLabel);
} else if (item instanceof BitmessageAddress) {
detailIntent = new Intent(this, AddressDetailActivity.class);
} else {
throw new IllegalArgumentException("Plaintext or BitmessageAddress expected, but " +
"was "
+ item.getClass().getSimpleName());
}
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
startActivity(detailIntent);
}
}
public boolean hasDetailPane() {
return twoPane;
}
public void setDetailView(Fragment fragment) {
if (twoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.message_detail_container, fragment)
.commit();
}
}
public void updateTitle(CharSequence title) {
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(title);
}
}
public Label getSelectedLabel() {
return selectedLabel;
}
public FabSpeedDial getFloatingActionButton() {
return fab;
}
public static MainActivity getInstance() {
if (instance == null) return null;
return instance.get();
}
}

View File

@ -0,0 +1,528 @@
/*
* 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.content.Intent
import android.graphics.Point
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.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import ch.dissem.apps.abit.drawer.ProfileImageListener
import ch.dissem.apps.abit.drawer.ProfileSelectionListener
import ch.dissem.apps.abit.listener.ListSelectionListener
import ch.dissem.apps.abit.repository.AndroidMessageRepository.Companion.LABEL_ARCHIVE
import ch.dissem.apps.abit.service.BitmessageService.Companion.isRunning
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.synchronization.SyncAdapter
import ch.dissem.apps.abit.util.Labels
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
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 com.github.amlcurran.showcaseview.ShowcaseView
import com.mikepenz.community_material_typeface_library.CommunityMaterial
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.materialdrawer.AccountHeader
import com.mikepenz.materialdrawer.AccountHeaderBuilder
import com.mikepenz.materialdrawer.Drawer
import com.mikepenz.materialdrawer.DrawerBuilder
import com.mikepenz.materialdrawer.model.*
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IProfile
import com.mikepenz.materialdrawer.model.interfaces.Nameable
import io.github.kobakei.materialfabspeeddial.FabSpeedDial
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.io.Serializable
import java.lang.ref.WeakReference
import java.util.*
/**
* An activity representing a list of Messages. This activity
* has different presentations for handset and tablet-size devices. On
* handsets, the activity presents a list of items, which when touched,
* lead to a [MessageDetailActivity] representing
* item details. On tablets, the activity presents the list of items and
* item details side-by-side using two vertical panes.
*
*
* The activity makes heavy use of fragments. The list of items is a
* [MessageListFragment] and the item details
* (if present) is a [MessageDetailFragment].
*
*
* This activity also implements the required
* [ListSelectionListener] interface
* to listen for item selections.
*
*/
class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
private var active: Boolean = false
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
var hasDetailPane: Boolean = false
private set
var selectedLabel: Label? = null
private set
private lateinit var bmc: BitmessageContext
private lateinit var accountHeader: AccountHeader
private lateinit var drawer: Drawer
private lateinit var nodeSwitch: SwitchDrawerItem
val floatingActionButton: FabSpeedDial
get() = fab
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
instance = WeakReference(this)
bmc = Singleton.getBitmessageContext(this)
setContentView(R.layout.activity_main)
fab.hide()
val toolbar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar)
val listFragment = MessageListFragment()
supportFragmentManager
.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
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
hasDetailPane = true
// In two-pane mode, list items should be given the
// 'activated' state when touched.
listFragment.setActivateOnItemClick(true)
}
createDrawer(toolbar)
// handle intents
val intent = intent
if (intent.hasExtra(EXTRA_SHOW_MESSAGE)) {
onItemSelected(intent.getSerializableExtra(EXTRA_SHOW_MESSAGE))
}
if (intent.hasExtra(EXTRA_REPLY_TO_MESSAGE)) {
val item = intent.getSerializableExtra(EXTRA_REPLY_TO_MESSAGE) as Plaintext
ComposeMessageActivity.launchReplyTo(this, item)
}
if (Preferences.useTrustedNode(this)) {
SyncAdapter.startSync(this)
} else {
SyncAdapter.stopSync(this)
}
if (drawer.isDrawerOpen) {
val lps = RelativeLayout.LayoutParams(ViewGroup
.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
lps.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
val margin = ((resources.displayMetrics.density * 12) as Number).toInt()
lps.setMargins(margin, margin, margin, margin)
ShowcaseView.Builder(this)
.withMaterialShowcase()
.setStyle(R.style.CustomShowcaseTheme)
.setContentTitle(R.string.full_node)
.setContentText(R.string.full_node_description)
.setTarget {
val view = drawer.stickyFooter
val location = IntArray(2)
view.getLocationInWindow(location)
val x = location[0] + 7 * view.width / 8
val y = location[1] + view.height / 2
Point(x, y)
}
.replaceEndButton(R.layout.showcase_button)
.hideOnTouchOutside()
.build()
.setButtonPosition(lps)
}
}
private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> {
if (active) {
val transaction = supportFragmentManager
.beginTransaction()
transaction.replace(R.id.item_list, listFragment)
val detailFragment = supportFragmentManager.findFragmentById(R.id.message_detail_container)
if (detailFragment != null) {
transaction.remove(detailFragment)
}
transaction.addToBackStack(null).commit()
if (hasDetailPane) {
// In two-pane mode, list items should be given the
// 'activated' state when touched.
listFragment.setActivateOnItemClick(true)
}
}
}
private fun createDrawer(toolbar: Toolbar) {
val profiles = ArrayList<IProfile<*>>()
profiles.add(ProfileSettingDrawerItem()
.withName(getString(R.string.add_identity))
.withDescription(getString(R.string.add_identity_summary))
.withIcon(IconicsDrawable(this, GoogleMaterial.Icon.gmd_add)
.actionBar()
.paddingDp(5)
.colorRes(R.color.icons))
.withIdentifier(ADD_IDENTITY.toLong())
)
profiles.add(ProfileSettingDrawerItem()
.withName(getString(R.string.manage_identity))
.withIcon(GoogleMaterial.Icon.gmd_settings)
.withIdentifier(MANAGE_IDENTITY.toLong())
)
// Create the AccountHeader
accountHeader = AccountHeaderBuilder()
.withActivity(this)
.withHeaderBackground(R.drawable.header)
.withProfiles(profiles)
.withOnAccountHeaderProfileImageListener(ProfileImageListener(this))
.withOnAccountHeaderListener(ProfileSelectionListener(this@MainActivity, supportFragmentManager))
.build()
if (profiles.size > 2) { // There's always the add and manage identity items
accountHeader.setActiveProfile(profiles[0], true)
}
val drawerItems = ArrayList<IDrawerItem<*, *>>()
drawerItems.add(PrimaryDrawerItem()
.withName(R.string.archive)
.withTag(LABEL_ARCHIVE)
.withIcon(CommunityMaterial.Icon.cmd_archive)
)
drawerItems.add(DividerDrawerItem())
drawerItems.add(PrimaryDrawerItem()
.withName(R.string.contacts_and_subscriptions)
.withIcon(GoogleMaterial.Icon.gmd_contacts))
drawerItems.add(PrimaryDrawerItem()
.withName(R.string.settings)
.withIcon(GoogleMaterial.Icon.gmd_settings))
nodeSwitch = SwitchDrawerItem()
.withIdentifier(ID_NODE_SWITCH)
.withName(R.string.full_node)
.withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
.withChecked(isRunning)
.withOnCheckedChangeListener { _, _, isChecked ->
if (isChecked) {
NetworkUtils.enableNode(this@MainActivity)
} else {
NetworkUtils.disableNode(this@MainActivity)
}
}
drawer = DrawerBuilder()
.withActivity(this)
.withToolbar(toolbar)
.withAccountHeader(accountHeader)
.withDrawerItems(drawerItems)
.addStickyDrawerItems(nodeSwitch)
.withOnDrawerItemClickListener(DrawerItemClickListener())
.withShowDrawerOnFirstLaunch(true)
.build()
loadDrawerItemsAsynchronously()
}
private fun loadDrawerItemsAsynchronously() {
doAsync {
val identities = bmc.addresses.getIdentities()
if (identities.isEmpty()) {
// Create an initial identity
Singleton.getIdentity(this@MainActivity)
}
uiThread {
for (identity in identities) {
addIdentityEntry(identity)
}
}
}
doAsync {
val labels = bmc.messages.getLabels()
uiThread {
if (intent.hasExtra(EXTRA_SHOW_LABEL)) {
selectedLabel = intent.getSerializableExtra(EXTRA_SHOW_LABEL) as Label
} else if (selectedLabel == null) {
selectedLabel = labels[0]
}
for (label in labels) {
addLabelEntry(label)
}
val selectedDrawerItem = drawer.getDrawerItem(selectedLabel)
if (selectedDrawerItem != null) {
drawer.setSelection(selectedDrawerItem)
}
}
}
}
override fun onBackPressed() {
val listFragment = supportFragmentManager.findFragmentById(R.id.item_list)
if (listFragment is ListHolder<*>) {
val listHolder = listFragment as ListHolder<*>
if (listHolder.showPreviousList()) {
val drawerItem = drawer.getDrawerItem(listHolder.currentLabel)
if (drawerItem != null) {
drawer.setSelection(drawerItem)
}
return
}
}
super.onBackPressed()
}
private inner class DrawerItemClickListener : Drawer.OnDrawerItemClickListener {
override fun onItemClick(view: View?, position: Int, item: IDrawerItem<*, *>): Boolean {
val itemList = supportFragmentManager.findFragmentById(R.id.item_list)
val tag = item.tag
if (tag is Label) {
selectedLabel = tag
if (itemList is MessageListFragment) {
itemList.updateList(tag)
} else {
val listFragment = MessageListFragment()
changeList(listFragment)
listFragment.updateList(tag)
}
return false
} else if (item is Nameable<*>) {
when (item.name.textRes) {
R.string.contacts_and_subscriptions -> {
if (itemList is AddressListFragment) {
itemList.updateList()
} else {
changeList(AddressListFragment())
}
return false
}
R.string.settings -> {
supportFragmentManager
.beginTransaction()
.replace(R.id.item_list, SettingsFragment())
.addToBackStack(null)
.commit()
return false
}
R.string.full_node -> return true
else -> return false
}
}
return false
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
savedInstanceState.putSerializable("selectedLabel", selectedLabel)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
selectedLabel = savedInstanceState.getSerializable("selectedLabel") as Label
val selectedItem = drawer.getDrawerItem(selectedLabel)
if (selectedItem != null) {
drawer.setSelection(selectedItem)
}
super.onRestoreInstanceState(savedInstanceState)
}
override fun onResume() {
updateUnread()
if (Preferences.isFullNodeActive(this) && Preferences.isConnectionAllowed(this@MainActivity)) {
NetworkUtils.enableNode(this, false)
}
updateNodeSwitch()
Singleton.getMessageListener(this).resetNotification()
active = true
super.onResume()
}
override fun onPause() {
super.onPause()
active = false
}
fun addIdentityEntry(identity: BitmessageAddress) {
val newProfile = ProfileDrawerItem()
.withIcon(Identicon(identity))
.withName(identity.toString())
.withNameShown(true)
.withEmail(identity.address)
.withTag(identity)
if (accountHeader.profiles != null) {
// we know that there are 2 setting elements.
// Set the new profile above them ;)
accountHeader.addProfile(
newProfile, accountHeader.profiles.size - 2)
} else {
accountHeader.addProfiles(newProfile)
}
}
private fun addLabelEntry(label: Label) {
val item = PrimaryDrawerItem()
.withName(label.toString())
.withTag(label)
.withIcon(Labels.getIcon(label))
.withIconColor(Labels.getColor(label))
drawer.addItemAtPosition(item, drawer.drawerItems.size - 3)
}
fun updateIdentityEntry(identity: BitmessageAddress) {
for (profile in accountHeader.profiles) {
if (profile is ProfileDrawerItem) {
if (identity == profile.tag) {
profile
.withName(identity.toString())
.withTag(identity)
return
}
}
}
}
fun removeIdentityEntry(identity: BitmessageAddress) {
for (profile in accountHeader.profiles) {
if (profile is ProfileDrawerItem) {
if (identity == profile.tag) {
accountHeader.removeProfile(profile)
return
}
}
}
}
fun updateUnread() {
for (item in drawer.drawerItems) {
if (item.tag is Label) {
val label = item.tag as Label
if (label !== LABEL_ARCHIVE) {
val unread = bmc.messages.countUnread(label)
if (unread > 0) {
(item as PrimaryDrawerItem).withBadge(unread.toString())
} else {
(item as PrimaryDrawerItem).withBadge(null as String?)
}
drawer.updateItem(item)
}
}
}
}
/**
* Callback method from [ListSelectionListener]
* indicating that the item with the given ID was selected.
*/
override fun onItemSelected(item: Serializable) {
if (hasDetailPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
val arguments = Bundle()
arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item)
val fragment = when (item) {
is Plaintext -> MessageDetailFragment()
is BitmessageAddress -> AddressDetailFragment()
else -> throw IllegalArgumentException("Plaintext or BitmessageAddress expected, but was ${item::class.simpleName}")
}
fragment.arguments = arguments
supportFragmentManager.beginTransaction()
.replace(R.id.message_detail_container, fragment)
.commit()
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
val detailIntent = when (item) {
is Plaintext -> {
Intent(this, MessageDetailActivity::class.java).apply {
putExtra(EXTRA_SHOW_LABEL, selectedLabel)
}
}
is BitmessageAddress -> Intent(this, AddressDetailActivity::class.java)
else -> throw IllegalArgumentException("Plaintext or BitmessageAddress expected, but was ${item::class.simpleName}")
}
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item)
startActivity(detailIntent)
}
}
fun setDetailView(fragment: Fragment) {
if (hasDetailPane) {
supportFragmentManager.beginTransaction()
.replace(R.id.message_detail_container, fragment)
.commit()
}
}
fun updateTitle(title: CharSequence) {
supportActionBar?.title = title
}
companion object {
val EXTRA_SHOW_MESSAGE = "ch.dissem.abit.ShowMessage"
val EXTRA_SHOW_LABEL = "ch.dissem.abit.ShowLabel"
val EXTRA_REPLY_TO_MESSAGE = "ch.dissem.abit.ReplyToMessage"
val ACTION_SHOW_INBOX = "ch.dissem.abit.ShowInbox"
val ADD_IDENTITY = 1
val MANAGE_IDENTITY = 2
private val ID_NODE_SWITCH: Long = 1
private var instance: WeakReference<MainActivity>? = null
fun updateNodeSwitch() {
val i = getInstance()
i?.apply {
runOnUiThread {
nodeSwitch.withChecked(Preferences.isFullNodeActive(i))
drawer.updateStickyFooterItem(nodeSwitch)
}
}
}
fun getInstance() = instance?.get()
}
}

View File

@ -1,63 +0,0 @@
package ch.dissem.apps.abit;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
import ch.dissem.bitmessage.entity.valueobject.Label;
/**
* An activity representing a single Message 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 MainActivity}.
* <p/>
* This activity is mostly just a 'shell' activity containing nothing
* more than a {@link MessageDetailFragment}.
*/
public class MessageDetailActivity extends DetailActivity {
private Label label;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 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) {
label = (Label) getIntent().getSerializableExtra(MainActivity.EXTRA_SHOW_LABEL);
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putSerializable(MessageDetailFragment.ARG_ITEM,
getIntent().getSerializableExtra(MessageDetailFragment.ARG_ITEM));
MessageDetailFragment fragment = new MessageDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.content, fragment)
.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent parentIntent = new Intent(this, MainActivity.class);
parentIntent.putExtra(MainActivity.EXTRA_SHOW_LABEL, label);
NavUtils.navigateUpTo(this, parentIntent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@ -0,0 +1,59 @@
package ch.dissem.apps.abit
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.NavUtils
import android.view.MenuItem
import ch.dissem.bitmessage.entity.valueobject.Label
/**
* An activity representing a single Message 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 [MainActivity].
*
* This activity is mostly just a 'shell' activity containing nothing
* more than a [MessageDetailFragment].
*/
class MessageDetailActivity : DetailActivity() {
private var label: Label? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 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) {
label = intent.getSerializableExtra(MainActivity.EXTRA_SHOW_LABEL) as Label
// Create the detail fragment and add it to the activity
// using a fragment transaction.
val arguments = Bundle()
arguments.putSerializable(MessageDetailFragment.ARG_ITEM,
intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM))
val fragment = MessageDetailFragment()
fragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.content, fragment)
.commit()
}
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
android.R.id.home -> {
val parentIntent = Intent(this, MainActivity::class.java)
parentIntent.putExtra(MainActivity.EXTRA_SHOW_LABEL, label)
NavUtils.navigateUpTo(this, parentIntent)
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@ -1,358 +0,0 @@
/*
* 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.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.view.IconicsImageView;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.Assets;
import ch.dissem.apps.abit.util.Drawables;
import ch.dissem.apps.abit.util.Labels;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.ports.MessageRepository;
import static android.text.util.Linkify.WEB_URLS;
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN;
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA;
import static ch.dissem.apps.abit.util.Strings.prepareMessageExtract;
/**
* A fragment representing a single Message detail screen.
* This fragment is either contained in a {@link MainActivity}
* in two-pane mode (on tablets) or a {@link MessageDetailActivity}
* on handsets.
*/
public class MessageDetailFragment 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 Plaintext item;
@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 = (Plaintext) getArguments().getSerializable(ARG_ITEM);
}
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_message_detail, container, false);
// Show the dummy content as text in a TextView.
if (item != null) {
((TextView) rootView.findViewById(R.id.subject)).setText(item.getSubject());
ImageView status = (ImageView) rootView.findViewById(R.id.status);
status.setImageResource(Assets.getStatusDrawable(item.getStatus()));
status.setContentDescription(getString(Assets.getStatusString(item.getStatus())));
BitmessageAddress sender = item.getFrom();
((ImageView) rootView.findViewById(R.id.avatar))
.setImageDrawable(new Identicon(sender));
((TextView) rootView.findViewById(R.id.sender)).setText(sender.toString());
if (item.getTo() != null) {
((TextView) rootView.findViewById(R.id.recipient)).setText(item.getTo().toString());
} else if (item.getType() == Plaintext.Type.BROADCAST) {
((TextView) rootView.findViewById(R.id.recipient)).setText(R.string.broadcast);
}
RecyclerView labelView = (RecyclerView) rootView.findViewById(R.id.labels);
LabelAdapter labelAdapter = new LabelAdapter(getActivity(), item.getLabels());
labelView.setAdapter(labelAdapter);
labelView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
TextView messageBody = (TextView) rootView.findViewById(R.id.text);
messageBody.setText(item.getText());
Linkify.addLinks(messageBody, WEB_URLS);
Linkify.addLinks(messageBody, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null,
new Linkify.TransformFilter() {
@Override
public String transformUrl(Matcher match, String url) {
return match.group();
}
}
);
messageBody.setLinksClickable(true);
messageBody.setTextIsSelectable(true);
boolean removed = false;
Iterator<Label> labels = item.getLabels().iterator();
while (labels.hasNext()) {
if (labels.next().getType() == Label.Type.UNREAD) {
labels.remove();
removed = true;
}
}
MessageRepository messageRepo = Singleton.getMessageRepository(inflater.getContext());
if (removed) {
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).updateUnread();
}
messageRepo.save(item);
}
List<Plaintext> parents = new ArrayList<>(item.getParents().size());
for (InventoryVector parentIV : item.getParents()) {
Plaintext parent = messageRepo.getMessage(parentIV);
if (parent != null) {
parents.add(parent);
}
}
showRelatedMessages(rootView, R.id.parents, parents);
showRelatedMessages(rootView, R.id.responses, messageRepo.findResponses(item));
}
return rootView;
}
private void showRelatedMessages(View rootView, @IdRes int id, List<Plaintext> messages) {
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(id);
RelatedMessageAdapter adapter = new RelatedMessageAdapter(getActivity(), messages);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.message, menu);
Drawables.addIcon(getActivity(), menu, R.id.reply, GoogleMaterial.Icon.gmd_reply);
Drawables.addIcon(getActivity(), menu, R.id.delete, GoogleMaterial.Icon.gmd_delete);
Drawables.addIcon(getActivity(), menu, R.id.mark_unread, GoogleMaterial.Icon
.gmd_markunread);
Drawables.addIcon(getActivity(), menu, R.id.archive, GoogleMaterial.Icon.gmd_archive);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
MessageRepository messageRepo = Singleton.getMessageRepository(getContext());
switch (menuItem.getItemId()) {
case R.id.reply:
ComposeMessageActivity.launchReplyTo(this, item);
return true;
case R.id.delete:
if (isInTrash(item)) {
messageRepo.remove(item);
} else {
item.getLabels().clear();
item.addLabels(messageRepo.getLabels(Label.Type.TRASH));
messageRepo.save(item);
}
getActivity().onBackPressed();
return true;
case R.id.mark_unread:
item.addLabels(messageRepo.getLabels(Label.Type.UNREAD));
messageRepo.save(item);
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).updateUnread();
}
return true;
case R.id.archive:
if (item.isUnread() && getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).updateUnread();
}
item.getLabels().clear();
messageRepo.save(item);
return true;
default:
return false;
}
}
public static boolean isInTrash(Plaintext item) {
for (Label label : item.getLabels()) {
if (label.getType() == Label.Type.TRASH) {
return true;
}
}
return false;
}
private static class RelatedMessageAdapter extends RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder> {
private final List<Plaintext> messages;
private final Context ctx;
private RelatedMessageAdapter(Context ctx, List<Plaintext> messages) {
this.messages = messages;
this.ctx = ctx;
}
@Override
public RelatedMessageAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View contactView = inflater.inflate(R.layout.item_message_minimized, parent, false);
// Return a new holder instance
return new RelatedMessageAdapter.ViewHolder(contactView);
}
// Involves populating data into the item through holder
@Override
public void onBindViewHolder(RelatedMessageAdapter.ViewHolder viewHolder, int position) {
// Get the data model based on position
Plaintext message = messages.get(position);
viewHolder.avatar.setImageDrawable(new Identicon(message.getFrom()));
viewHolder.status.setImageResource(Assets.getStatusDrawable(message.getStatus()));
viewHolder.sender.setText(message.getFrom().toString());
viewHolder.extract.setText(prepareMessageExtract(message.getText()));
viewHolder.item = message;
}
// Returns the total count of items in the list
@Override
public int getItemCount() {
return messages.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView avatar;
private final ImageView status;
private final TextView sender;
private final TextView extract;
private Plaintext item;
ViewHolder(final View itemView) {
super(itemView);
avatar = (ImageView) itemView.findViewById(R.id.avatar);
status = (ImageView) itemView.findViewById(R.id.status);
sender = (TextView) itemView.findViewById(R.id.sender);
extract = (TextView) itemView.findViewById(R.id.text);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ctx instanceof MainActivity) {
((MainActivity) ctx).onItemSelected(item);
} else {
Intent detailIntent;
detailIntent = new Intent(ctx, MessageDetailActivity.class);
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item);
ctx.startActivity(detailIntent);
}
}
});
}
}
}
private static class LabelAdapter extends
RecyclerView.Adapter<LabelAdapter.ViewHolder> {
private final List<Label> labels;
private final Context ctx;
private LabelAdapter(Context ctx, Set<Label> labels) {
this.labels = new ArrayList<>(labels);
this.ctx = ctx;
}
@Override
public LabelAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
// Inflate the custom layout
View contactView = inflater.inflate(R.layout.item_label, parent, false);
// Return a new holder instance
return new ViewHolder(contactView);
}
// Involves populating data into the item through holder
@Override
public void onBindViewHolder(LabelAdapter.ViewHolder viewHolder, int position) {
// Get the data model based on position
Label label = labels.get(position);
viewHolder.icon.setColor(Labels.getColor(label));
viewHolder.icon.setIcon(Labels.getIcon(label));
viewHolder.label.setText(Labels.getText(label, ctx));
}
// Returns the total count of items in the list
@Override
public int getItemCount() {
return labels.size();
}
// Provide a direct reference to each of the views within a data item
// Used to cache the views within the item layout for fast access
static class ViewHolder extends RecyclerView.ViewHolder {
// Your holder should contain a member variable
// for any view that will be set as you render a row
public IconicsImageView icon;
public TextView label;
// We also create a constructor that accepts the entire item row
// and does the view lookups to find each subview
ViewHolder(View itemView) {
// Stores the itemView in a public final member variable that can be used
// to access the context from any ViewHolder instance.
super(itemView);
icon = (IconicsImageView) itemView.findViewById(R.id.icon);
label = (TextView) itemView.findViewById(R.id.label);
}
}
}
}

View File

@ -0,0 +1,292 @@
/*
* 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.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.annotation.IdRes
import android.support.v4.app.Fragment
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.text.util.Linkify
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.mikepenz.google_material_typeface_library.GoogleMaterial
import com.mikepenz.iconics.view.IconicsImageView
import java.util.ArrayList
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.Assets
import ch.dissem.apps.abit.util.Drawables
import ch.dissem.apps.abit.util.Labels
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.Label
import android.text.util.Linkify.WEB_URLS
import ch.dissem.apps.abit.util.Constants.BITMESSAGE_ADDRESS_PATTERN
import ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
import kotlinx.android.synthetic.main.fragment_message_detail.*
/**
* A fragment representing a single Message detail screen.
* This fragment is either contained in a [MainActivity]
* in two-pane mode (on tablets) or a [MessageDetailActivity]
* on handsets.
*/
class MessageDetailFragment : Fragment() {
/**
* The content this fragment is presenting.
*/
private var item: Plaintext? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments.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 = arguments.getSerializable(ARG_ITEM) as Plaintext
}
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_message_detail, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Show the dummy content as text in a TextView.
item?.let { item ->
subject.text = item.subject
status.setImageResource(Assets.getStatusDrawable(item.status))
status.contentDescription = getString(Assets.getStatusString(item.status))
avatar.setImageDrawable(Identicon(item.from))
sender.text = item.from.toString()
item.to?.let { to ->
recipient.text = to.toString()
} ?: {
if (item.type == Plaintext.Type.BROADCAST) {
recipient.setText(R.string.broadcast)
}
}.invoke()
val labelAdapter = LabelAdapter(activity, item.labels)
labels.adapter = labelAdapter
labels.layoutManager = GridLayoutManager(activity, 2)
text.text = item.text
Linkify.addLinks(text, WEB_URLS)
Linkify.addLinks(text, BITMESSAGE_ADDRESS_PATTERN, BITMESSAGE_URL_SCHEMA, null,
Linkify.TransformFilter { match, _ -> match.group() }
)
text.linksClickable = true
text.setTextIsSelectable(true)
var removed = false
val labels = item.labels.iterator()
while (labels.hasNext()) {
if (labels.next().type == Label.Type.UNREAD) {
labels.remove()
removed = true
}
}
val messageRepo = Singleton.getMessageRepository(context)
if (removed) {
if (activity is MainActivity) {
(activity as MainActivity).updateUnread()
}
messageRepo.save(item)
}
val parents = ArrayList<Plaintext>(item.parents.size)
for (parentIV in item.parents) {
val parent = messageRepo.getMessage(parentIV)
if (parent != null) {
parents.add(parent)
}
}
showRelatedMessages(view, R.id.parents, parents)
showRelatedMessages(view, R.id.responses, messageRepo.findResponses(item))
}
}
private fun showRelatedMessages(rootView: View, @IdRes id: Int, messages: List<Plaintext>) {
val recyclerView = rootView.findViewById(id) as RecyclerView
val adapter = RelatedMessageAdapter(activity, messages)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(activity)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater?) {
inflater!!.inflate(R.menu.message, menu)
Drawables.addIcon(activity, menu, R.id.reply, GoogleMaterial.Icon.gmd_reply)
Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete)
Drawables.addIcon(activity, menu, R.id.mark_unread, GoogleMaterial.Icon
.gmd_markunread)
Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
val messageRepo = Singleton.getMessageRepository(context)
item?.let { item ->
when (menuItem.itemId) {
R.id.reply -> {
ComposeMessageActivity.launchReplyTo(this, item)
return true
}
R.id.delete -> {
if (isInTrash(item)) {
messageRepo.remove(item)
} else {
item.labels.clear()
item.addLabels(messageRepo.getLabels(Label.Type.TRASH))
messageRepo.save(item)
}
activity.onBackPressed()
return true
}
R.id.mark_unread -> {
item.addLabels(messageRepo.getLabels(Label.Type.UNREAD))
messageRepo.save(item)
if (activity is MainActivity) {
(activity as MainActivity).updateUnread()
}
return true
}
R.id.archive -> {
if (item.isUnread() && activity is MainActivity) {
(activity as MainActivity).updateUnread()
}
item.labels.clear()
messageRepo.save(item)
return true
}
else -> return false
}
}
return false
}
private class RelatedMessageAdapter internal constructor(private val ctx: Context, private val messages: List<Plaintext>) : RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RelatedMessageAdapter.ViewHolder {
val context = parent.context
val inflater = LayoutInflater.from(context)
// Inflate the custom layout
val contactView = inflater.inflate(R.layout.item_message_minimized, parent, false)
// Return a new holder instance
return ViewHolder(contactView)
}
// Involves populating data into the item through holder
override fun onBindViewHolder(viewHolder: RelatedMessageAdapter.ViewHolder, position: Int) {
// Get the data model based on position
val message = messages[position]
viewHolder.avatar.setImageDrawable(Identicon(message.from))
viewHolder.status.setImageResource(Assets.getStatusDrawable(message.status))
viewHolder.sender.text = message.from.toString()
viewHolder.extract.text = prepareMessageExtract(message.text)
viewHolder.item = message
}
// Returns the total count of items in the list
override fun getItemCount() = messages.size
internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
internal val avatar = itemView.findViewById(R.id.avatar) as ImageView
internal val status = itemView.findViewById(R.id.status) as ImageView
internal val sender = itemView.findViewById(R.id.sender) as TextView
internal val extract = itemView.findViewById(R.id.text) as TextView
internal var item: Plaintext? = null
init {
itemView.setOnClickListener {
if (ctx is MainActivity) {
item?.let { ctx.onItemSelected(it) }
} else {
val detailIntent = Intent(ctx, MessageDetailActivity::class.java)
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item)
ctx.startActivity(detailIntent)
}
}
}
}
}
private class LabelAdapter internal constructor(private val ctx: Context, labels: Set<Label>) : RecyclerView.Adapter<LabelAdapter.ViewHolder>() {
private val labels = labels.toMutableList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LabelAdapter.ViewHolder {
val context = parent.context
val inflater = LayoutInflater.from(context)
// Inflate the custom layout
val contactView = inflater.inflate(R.layout.item_label, parent, false)
// Return a new holder instance
return ViewHolder(contactView)
}
// Involves populating data into the item through holder
override fun onBindViewHolder(viewHolder: LabelAdapter.ViewHolder, position: Int) {
// Get the data model based on position
val label = labels[position]
viewHolder.icon.setColor(Labels.getColor(label))
viewHolder.icon.setIcon(Labels.getIcon(label))
viewHolder.label.text = Labels.getText(label, ctx)
}
override fun getItemCount() = labels.size
internal class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var icon = itemView.findViewById(R.id.icon) as IconicsImageView
var label = itemView.findViewById(R.id.label) as TextView
}
}
companion object {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
val ARG_ITEM = "item"
fun isInTrash(item: Plaintext?) = item?.labels?.any { it.type == Label.Type.TRASH } ?: false
}
}

View File

@ -1,361 +0,0 @@
/*
* 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.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
import java.util.List;
import java.util.Stack;
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter;
import ch.dissem.apps.abit.listener.ListSelectionListener;
import ch.dissem.apps.abit.repository.AndroidMessageRepository;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.apps.abit.util.FabUtils;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_BROADCAST;
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_IDENTITY;
import static ch.dissem.apps.abit.MessageDetailFragment.isInTrash;
/**
* A list fragment representing a list of Messages. This fragment
* also supports tablet devices by allowing list items to be given an
* 'activated' state upon selection. This helps indicate which item is
* currently being viewed in a {@link MessageDetailFragment}.
* <p/>
* Activities containing this fragment MUST implement the {@link ListSelectionListener}
* interface.
*/
public class MessageListFragment extends Fragment implements ListHolder<Label> {
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private SwipeableMessageAdapter adapter;
private RecyclerView.Adapter wrappedAdapter;
private RecyclerViewSwipeManager recyclerViewSwipeManager;
private RecyclerViewTouchActionGuardManager recyclerViewTouchActionGuardManager;
private Label currentLabel;
private MenuItem emptyTrashMenuItem;
private AndroidMessageRepository messageRepo;
private boolean activateOnItemClick;
private Stack<Label> backStack = new Stack<>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onResume() {
super.onResume();
MainActivity activity = (MainActivity) getActivity();
initFab(activity);
messageRepo = Singleton.getMessageRepository(activity);
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;
}
doUpdateList(label);
}
private void doUpdateList(final Label label) {
adapter.clear(label);
if (label == null) {
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).updateTitle(getString(R.string.app_name));
}
adapter.notifyDataSetChanged();
return;
}
currentLabel = label;
if (emptyTrashMenuItem != null) {
emptyTrashMenuItem.setVisible(label.getType() == Label.Type.TRASH);
}
if (getActivity() instanceof MainActivity) {
MainActivity actionBarListener = (MainActivity) getActivity();
if ("archive".equals(label.toString())) {
actionBarListener.updateTitle(getString(R.string.archive));
} else {
actionBarListener.updateTitle(label.toString());
}
}
new AsyncTask<Void, Plaintext, Void>() {
@Override
protected Void doInBackground(Void... params) {
List<Long> ids = messageRepo.findMessageIds(label);
for (Long id : ids) {
Plaintext message = messageRepo.getMessage(id);
publishProgress(message);
}
return null;
}
@Override
protected void onProgressUpdate(Plaintext... values) {
if (adapter != null) {
for (Plaintext message : values) {
adapter.add(message);
}
}
}
}.execute();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_message_list, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
// touch guard manager (this class is required to suppress scrolling while swipe-dismiss
// animation is running)
recyclerViewTouchActionGuardManager = new RecyclerViewTouchActionGuardManager();
recyclerViewTouchActionGuardManager.setInterceptVerticalScrollingWhileAnimationRunning
(true);
recyclerViewTouchActionGuardManager.setEnabled(true);
// swipe manager
recyclerViewSwipeManager = new RecyclerViewSwipeManager();
//adapter
adapter = new SwipeableMessageAdapter();
adapter.setActivateOnItemClick(activateOnItemClick);
adapter.setEventListener(new SwipeableMessageAdapter.EventListener() {
@Override
public void onItemDeleted(Plaintext item) {
if (isInTrash(item)) {
messageRepo.remove(item);
} else {
item.getLabels().clear();
item.addLabels(messageRepo.getLabels(Label.Type.TRASH));
messageRepo.save(item);
}
}
@Override
public void onItemArchived(Plaintext item) {
item.getLabels().clear();
messageRepo.save(item);
}
@Override
public void onItemViewClicked(View v) {
int position = recyclerView.getChildAdapterPosition(v);
adapter.setSelectedPosition(position);
if (position != RecyclerView.NO_POSITION) {
Plaintext item = adapter.getItem(position);
((MainActivity) getActivity()).onItemSelected(item);
}
}
});
// wrap for swiping
wrappedAdapter = recyclerViewSwipeManager.createWrappedAdapter(adapter);
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
// Change animations are enabled by default since support-v7-recyclerview v22.
// Disable the change animation in order to make turning back animation of swiped item
// works properly.
animator.setSupportsChangeAnimations(false);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(wrappedAdapter); // requires *wrapped* adapter
recyclerView.setItemAnimator(animator);
recyclerView.addItemDecoration(new SimpleListDividerDecorator(
ContextCompat.getDrawable(getContext(), R.drawable.list_divider_h), true));
// NOTE:
// The initialization order is very important! This order determines the priority of
// touch event handling.
//
// priority: TouchActionGuard > Swipe > DragAndDrop
recyclerViewTouchActionGuardManager.attachRecyclerView(recyclerView);
recyclerViewSwipeManager.attachRecyclerView(recyclerView);
return rootView;
}
private void initFab(MainActivity context){
FabSpeedDialMenu menu = new FabSpeedDialMenu(context);
menu.add(R.string.broadcast).setIcon(R.drawable.ic_action_broadcast);
menu.add(R.string.personal_message).setIcon(R.drawable.ic_action_personal);
FabUtils.initFab(context, R.drawable.ic_action_compose_message, menu)
.addOnMenuItemClickListener(new FabSpeedDial.OnMenuItemClickListener() {
@Override
public void onMenuItemClick(FloatingActionButton floatingActionButton, @Nullable TextView textView, int itemId) {
BitmessageAddress identity = Singleton.getIdentity(getActivity());
if (identity == null) {
Toast.makeText(getActivity(), R.string.no_identity_warning,
Toast.LENGTH_LONG).show();
} else {
switch (itemId) {
case 1: {
Intent intent = new Intent(getActivity(), ComposeMessageActivity.class);
intent.putExtra(EXTRA_IDENTITY, identity);
intent.putExtra(EXTRA_BROADCAST, true);
startActivity(intent);
break;
}
case 2: {
Intent intent = new Intent(getActivity(), ComposeMessageActivity.class);
intent.putExtra(EXTRA_IDENTITY, identity);
startActivity(intent);
break;
}
default:
break;
}
}
}
});
}
@Override
public void onDestroyView() {
if (recyclerViewSwipeManager != null) {
recyclerViewSwipeManager.release();
recyclerViewSwipeManager = null;
}
if (recyclerViewTouchActionGuardManager != null) {
recyclerViewTouchActionGuardManager.release();
recyclerViewTouchActionGuardManager = null;
}
if (recyclerView != null) {
recyclerView.setItemAnimator(null);
recyclerView.setAdapter(null);
recyclerView = null;
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter);
wrappedAdapter = null;
}
adapter = null;
layoutManager = null;
super.onDestroyView();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.message_list, menu);
emptyTrashMenuItem = menu.findItem(R.id.empty_trash);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.empty_trash:
if (currentLabel.getType() != Label.Type.TRASH) return true;
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
for (Plaintext message : messageRepo.findMessages(currentLabel)) {
messageRepo.remove(message);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
updateList(currentLabel);
}
}.execute();
return true;
default:
return false;
}
}
@Override
public void setActivateOnItemClick(boolean activateOnItemClick) {
if (adapter != null) {
adapter.setActivateOnItemClick(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

@ -0,0 +1,302 @@
/*
* 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.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.content.ContextCompat
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.*
import android.widget.Toast
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
import ch.dissem.apps.abit.listener.ListSelectionListener
import ch.dissem.apps.abit.repository.AndroidMessageRepository
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.FabUtils
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.Label
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.decoration.SimpleListDividerDecorator
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
import kotlinx.android.synthetic.main.fragment_message_list.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.util.*
/**
* A list fragment representing a list of Messages. This fragment
* also supports tablet devices by allowing list items to be given an
* 'activated' state upon selection. This helps indicate which item is
* currently being viewed in a [MessageDetailFragment].
*
*
* Activities containing this fragment MUST implement the [ListSelectionListener]
* interface.
*/
class MessageListFragment : Fragment(), ListHolder<Label> {
private var layoutManager: RecyclerView.LayoutManager? = null
private var swipeableMessageAdapter: SwipeableMessageAdapter? = null
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
override var currentLabel: Label? = null
private var emptyTrashMenuItem: MenuItem? = null
private lateinit var messageRepo: AndroidMessageRepository
private var activateOnItemClick: Boolean = false
private val backStack = Stack<Label>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onResume() {
super.onResume()
val activity = activity as MainActivity
initFab(activity)
messageRepo = Singleton.getMessageRepository(activity)
if (backStack.isEmpty()) {
doUpdateList(activity.selectedLabel)
} else {
doUpdateList(backStack.peek())
}
}
override fun updateList(label: Label) {
if (currentLabel != null && currentLabel != label) {
backStack.push(currentLabel)
}
if (!isResumed) {
currentLabel = label
return
}
doUpdateList(label)
}
private fun doUpdateList(label: Label?) {
val mainActivity = activity as? MainActivity
swipeableMessageAdapter?.clear(label)
if (label == null) {
mainActivity?.updateTitle(getString(R.string.app_name))
swipeableMessageAdapter?.notifyDataSetChanged()
return
}
currentLabel = label
emptyTrashMenuItem?.isVisible = label.type == Label.Type.TRASH
mainActivity?.apply {
if ("archive" == label.toString()) {
updateTitle(getString(R.string.archive))
} else {
updateTitle(label.toString())
}
}
doAsync {
messageRepo.findMessageIds(label)
.map { messageRepo.getMessage(it) }
.forEach { message ->
uiThread {
swipeableMessageAdapter?.add(message)
}
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_message_list, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
// touch guard manager (this class is required to suppress scrolling while swipe-dismiss
// animation is running)
val touchActionGuardManager = RecyclerViewTouchActionGuardManager().apply {
setInterceptVerticalScrollingWhileAnimationRunning(true)
isEnabled = true
}
// swipe manager
val swipeManager = RecyclerViewSwipeManager()
//swipeableMessageAdapter
val adapter = SwipeableMessageAdapter().apply {
setActivateOnItemClick(activateOnItemClick)
}
adapter.eventListener = object : SwipeableMessageAdapter.EventListener {
override fun onItemDeleted(item: Plaintext) {
if (MessageDetailFragment.isInTrash(item)) {
messageRepo.remove(item)
} else {
item.labels.clear()
item.addLabels(messageRepo.getLabels(Label.Type.TRASH))
messageRepo.save(item)
}
}
override fun onItemArchived(item: Plaintext) {
item.labels.clear()
messageRepo.save(item)
}
override fun onItemViewClicked(v: View?) {
val position = recycler_view.getChildAdapterPosition(v)
adapter.setSelectedPosition(position)
if (position != RecyclerView.NO_POSITION) {
val item = adapter.getItem(position)
(activity as MainActivity).onItemSelected(item)
}
}
}
// wrap for swiping
wrappedAdapter = swipeManager.createWrappedAdapter(adapter)
val animator = SwipeDismissItemAnimator()
// Change animations are enabled by default since support-v7-recyclerview v22.
// Disable the change animation in order to make turning back animation of swiped item
// works properly.
animator.supportsChangeAnimations = false
recycler_view.layoutManager = layoutManager
recycler_view.adapter = wrappedAdapter // requires *wrapped* swipeableMessageAdapter
recycler_view.itemAnimator = animator
recycler_view.addItemDecoration(SimpleListDividerDecorator(
ContextCompat.getDrawable(context, R.drawable.list_divider_h), true))
// NOTE:
// The initialization order is very important! This order determines the priority of
// touch event handling.
//
// priority: TouchActionGuard > Swipe > DragAndDrop
touchActionGuardManager.attachRecyclerView(recycler_view)
swipeManager.attachRecyclerView(recycler_view)
recyclerViewTouchActionGuardManager = touchActionGuardManager
recyclerViewSwipeManager = swipeManager
this.swipeableMessageAdapter = adapter
}
private fun initFab(context: MainActivity) {
val menu = FabSpeedDialMenu(context)
menu.add(R.string.broadcast).setIcon(R.drawable.ic_action_broadcast)
menu.add(R.string.personal_message).setIcon(R.drawable.ic_action_personal)
FabUtils.initFab(context, R.drawable.ic_action_compose_message, menu)
.addOnMenuItemClickListener { _, _, itemId ->
val identity = Singleton.getIdentity(activity)
if (identity == null) {
Toast.makeText(activity, R.string.no_identity_warning,
Toast.LENGTH_LONG).show()
} else {
when (itemId) {
1 -> {
val intent = Intent(activity, ComposeMessageActivity::class.java)
intent.putExtra(EXTRA_IDENTITY, identity)
intent.putExtra(EXTRA_BROADCAST, true)
startActivity(intent)
}
2 -> {
val intent = Intent(activity, ComposeMessageActivity::class.java)
intent.putExtra(EXTRA_IDENTITY, identity)
startActivity(intent)
}
else -> {
}
}
}
}
}
override fun onDestroyView() {
recyclerViewSwipeManager?.release()
recyclerViewSwipeManager = null
recyclerViewTouchActionGuardManager?.release()
recyclerViewTouchActionGuardManager = null
recycler_view.itemAnimator = null
recycler_view.adapter = null
wrappedAdapter?.let { WrapperAdapterUtils.releaseAll(it) }
wrappedAdapter = null
swipeableMessageAdapter = null
layoutManager = null
super.onDestroyView()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.message_list, menu)
emptyTrashMenuItem = menu.findItem(R.id.empty_trash)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
currentLabel?.let { currentLabel ->
when (item.itemId) {
R.id.empty_trash -> {
if (currentLabel.type != Label.Type.TRASH) return true
doAsync {
for (message in messageRepo.findMessages(currentLabel)) {
messageRepo.remove(message)
}
uiThread {
updateList(currentLabel)
}
}
return true
}
else -> return false
}
}
return false
}
override fun setActivateOnItemClick(activateOnItemClick: Boolean) {
swipeableMessageAdapter?.setActivateOnItemClick(activateOnItemClick)
this.activateOnItemClick = activateOnItemClick
}
override fun showPreviousList(): Boolean {
return if (backStack.isEmpty()) {
false
} else {
doUpdateList(backStack.pop())
true
}
}
}

View File

@ -42,6 +42,7 @@ import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.support.v4.indeterminateProgressDialog
import org.jetbrains.anko.support.v4.startActivity
import org.jetbrains.anko.uiThread
import java.io.File
import java.io.FileOutputStream
@ -59,13 +60,13 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
findPreference("about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val libsBuilder = LibsBuilder()
.withActivityTitle(activity.getString(R.string.about))
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.withAboutDescription(getString(R.string.about_app))
.withActivityTitle(activity.getString(R.string.about))
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.withAboutDescription(getString(R.string.about_app))
val activity = activity as MainActivity
if (activity.hasDetailPane()) {
if (activity.hasDetailPane) {
activity.setDetailView(libsBuilder.supportFragment())
} else {
libsBuilder.start(getActivity())
@ -77,7 +78,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
val ctx = activity.applicationContext
cleanup.isEnabled = false
Toast.makeText(ctx, R.string.cleanup_notification_start, Toast
.LENGTH_SHORT).show()
.LENGTH_SHORT).show()
doAsync {
val bmc = Singleton.getBitmessageContext(ctx)
@ -87,9 +88,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
uiThread {
Toast.makeText(
ctx,
R.string.cleanup_notification_end,
Toast.LENGTH_LONG
ctx,
R.string.cleanup_notification_end,
Toast.LENGTH_LONG
).show()
cleanup.isEnabled = true
}
@ -108,7 +109,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
val addressRepo = Singleton.getAddressRepository(context)
val exportContacts = ContactExport.exportContacts(addressRepo.getContacts())
zip.write(
exportContacts.toJsonString(true).toByteArray()
exportContacts.toJsonString(true).toByteArray()
)
zip.closeEntry()
@ -116,13 +117,13 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
zip.putNextEntry(ZipEntry("labels.json"))
val exportLabels = MessageExport.exportLabels(messageRepo.getLabels())
zip.write(
exportLabels.toJsonString(true).toByteArray()
exportLabels.toJsonString(true).toByteArray()
)
zip.closeEntry()
zip.putNextEntry(ZipEntry("messages.json"))
val exportMessages = MessageExport.exportMessages(messageRepo.getAllMessages())
zip.write(
exportMessages.toJsonString(true).toByteArray()
exportMessages.toJsonString(true).toByteArray()
)
zip.closeEntry()
}
@ -151,10 +152,10 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
findPreference("status").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val activity = activity as MainActivity
if (activity.hasDetailPane()) {
if (activity.hasDetailPane) {
activity.setDetailView(StatusFragment())
} else {
startActivity(Intent(getActivity(), StatusActivity::class.java))
startActivity<StatusActivity>()
}
return@OnPreferenceClickListener true
}
@ -217,7 +218,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
super.onAttach(ctx)
(ctx as? MainActivity)?.floatingActionButton?.hide()
PreferenceManager.getDefaultSharedPreferences(ctx)
.registerOnSharedPreferenceChangeListener(this)
.registerOnSharedPreferenceChangeListener(this)
(ctx as? MainActivity)?.updateTitle(getString(R.string.settings))
}

View File

@ -1,61 +0,0 @@
/*
* 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.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.widget.TextView;
import com.mikepenz.materialize.MaterializeBuilder;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
public class StatusActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_status);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(false);
new MaterializeBuilder()
.withActivity(this)
.withStatusBarColorRes(R.color.colorPrimaryDark)
.withTranslucentStatusBarProgrammatically(true)
.withStatusBarPadding(true)
.build();
BitmessageContext bmc = Singleton.getBitmessageContext(this);
StringBuilder status = new StringBuilder();
for (BitmessageAddress address : bmc.addresses().getIdentities()) {
status.append(address.getAddress()).append('\n');
}
status.append('\n');
status.append(bmc.status());
((TextView) findViewById(R.id.content)).setText(status);
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import ch.dissem.apps.abit.service.Singleton
import com.mikepenz.materialize.MaterializeBuilder
import kotlinx.android.synthetic.main.activity_status.*
class StatusActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_status)
setSupportActionBar(toolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setHomeButtonEnabled(false)
MaterializeBuilder()
.withActivity(this)
.withStatusBarColorRes(R.color.colorPrimaryDark)
.withTranslucentStatusBarProgrammatically(true)
.withStatusBarPadding(true)
.build()
val bmc = Singleton.getBitmessageContext(this)
val status = StringBuilder()
for (address in bmc.addresses.getIdentities()) {
status.append(address.address).append('\n')
}
status.append('\n')
status.append(bmc.status())
content.text = status
}
}

View File

@ -1,49 +0,0 @@
/*
* 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

@ -0,0 +1,46 @@
/*
* 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.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
class StatusFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_status, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bmc = Singleton.getBitmessageContext(context)
val status = StringBuilder()
for (address in bmc.addresses.getIdentities()) {
status.append(address.address).append('\n')
}
status.append('\n')
status.append(bmc.status())
(view.findViewById(R.id.content) as TextView).text = status
}
}

View File

@ -1,99 +0,0 @@
/*
* 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.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.entity.BitmessageAddress;
/**
* @author Christian Basler
*/
public class AddressSelectorAdapter
extends RecyclerView.Adapter<AddressSelectorAdapter.ViewHolder> {
private final List<Selectable<BitmessageAddress>> data;
public AddressSelectorAdapter(List<BitmessageAddress> identities) {
data = new ArrayList<>(identities.size());
for (BitmessageAddress identity : identities) {
data.add(new Selectable<>(identity));
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final View v = inflater.inflate(R.layout.select_identity_row, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Selectable<BitmessageAddress> selectable = data.get(position);
holder.data = selectable;
holder.checkbox.setChecked(selectable.selected);
holder.checkbox.setText(selectable.data.toString());
holder.address.setText(selectable.data.getAddress());
}
@Override
public int getItemCount() {
return data.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
public Selectable<BitmessageAddress> data;
public final CheckBox checkbox;
public final TextView address;
private ViewHolder(View v) {
super(v);
checkbox = (CheckBox) v.findViewById(R.id.checkbox);
address = (TextView) v.findViewById(R.id.address);
checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if (data != null) {
data.selected = isChecked;
}
}
});
}
}
public List<BitmessageAddress> getSelected() {
List<BitmessageAddress> result = new LinkedList<>();
for (Selectable<BitmessageAddress> selectable : data) {
if (selectable.selected) {
result.add(selectable.data);
}
}
return result;
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.adapter
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.TextView
import ch.dissem.apps.abit.R
import ch.dissem.bitmessage.entity.BitmessageAddress
import java.util.*
/**
* @author Christian Basler
*/
class AddressSelectorAdapter(identities: List<BitmessageAddress>) : RecyclerView.Adapter<AddressSelectorAdapter.ViewHolder>() {
private val data = identities.map { Selectable(it) }.toMutableList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val v = inflater.inflate(R.layout.select_identity_row, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val selectable = data[position]
holder.data = selectable
holder.checkbox.isChecked = selectable.selected
holder.checkbox.text = selectable.data.toString()
holder.address.text = selectable.data.address
}
override fun getItemCount() = data.size
class ViewHolder internal constructor(v: View) : RecyclerView.ViewHolder(v) {
var data: Selectable<BitmessageAddress>? = null
val checkbox = v.findViewById(R.id.checkbox) as CheckBox
val address = v.findViewById(R.id.address) as TextView
init {
checkbox.setOnCheckedChangeListener { _, isChecked ->
data?.selected = isChecked
}
}
}
val selected: List<BitmessageAddress>
get() {
val result = LinkedList<BitmessageAddress>()
for (selectable in data) {
if (selectable.selected) {
result.add(selectable.data)
}
}
return result
}
}

View File

@ -14,16 +14,16 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.adapter;
package ch.dissem.apps.abit.adapter
import ch.dissem.apps.abit.util.PRNGFixes;
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography;
import ch.dissem.apps.abit.util.PRNGFixes
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
/**
* @author Christian Basler
*/
public class AndroidCryptography extends SpongyCryptography {
public AndroidCryptography() {
PRNGFixes.apply();
class AndroidCryptography : SpongyCryptography() {
init {
PRNGFixes.apply()
}
}

View File

@ -1,140 +0,0 @@
/*
* 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.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.entity.BitmessageAddress;
/**
* An adapter for contacts. Can be filtered by alias or address.
*/
public class ContactAdapter extends BaseAdapter implements Filterable {
private final LayoutInflater inflater;
private final List<BitmessageAddress> originalData;
private List<BitmessageAddress> data;
public ContactAdapter(Context ctx) {
inflater = LayoutInflater.from(ctx);
originalData = Singleton.getAddressRepository(ctx).getContacts();
data = originalData;
}
@Override
public int getCount() {
return data.size();
}
@Override
public BitmessageAddress getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.contact_row, parent, false);
}
BitmessageAddress item = getItem(position);
((ImageView) convertView.findViewById(R.id.avatar)).setImageDrawable(new Identicon(item));
((TextView) convertView.findViewById(R.id.name)).setText(item.toString());
((TextView) convertView.findViewById(R.id.address)).setText(item.getAddress());
return convertView;
}
@Override
public Filter getFilter() {
return new ContactFilter();
}
private class ContactFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (prefix == null || prefix.length() == 0) {
results.values = originalData;
results.count = originalData.size();
} else {
String prefixString = prefix.toString().toLowerCase();
final ArrayList<BitmessageAddress> newValues = new ArrayList<>();
for (int i = 0; i < originalData.size(); i++) {
final BitmessageAddress value = originalData.get(i);
// First match against the whole, non-splitted value
if (value.getAlias() != null) {
String alias = value.getAlias().toLowerCase();
if (alias.startsWith(prefixString)) {
newValues.add(value);
} else {
final String[] words = alias.split(" ");
for (String word : words) {
if (word.startsWith(prefixString)) {
newValues.add(value);
break;
}
}
}
} else {
String address = value.getAddress().toLowerCase();
if (address.contains(prefixString)) {
newValues.add(value);
}
}
}
results.values = newValues;
results.count = newValues.size();
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//noinspection unchecked
data = (List<BitmessageAddress>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
}

View File

@ -0,0 +1,132 @@
/*
* 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.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.Filter
import android.widget.Filterable
import android.widget.ImageView
import android.widget.TextView
import java.util.ArrayList
import ch.dissem.apps.abit.Identicon
import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.bitmessage.entity.BitmessageAddress
/**
* An adapter for contacts. Can be filtered by alias or address.
*/
class ContactAdapter(ctx: Context) : BaseAdapter(), Filterable {
private val inflater: LayoutInflater
private val originalData: List<BitmessageAddress>
private var data: List<BitmessageAddress> = emptyList()
init {
inflater = LayoutInflater.from(ctx)
originalData = Singleton.getAddressRepository(ctx).getContacts()
data = originalData
}
override fun getCount() = data.size
override fun getItem(position: Int) = data[position]
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val viewHolder = if (convertView == null) {
ViewHolder(inflater.inflate(R.layout.contact_row, parent, false))
} else {
convertView.tag as ViewHolder
}
val item = getItem(position)
viewHolder.avatar.setImageDrawable(Identicon(item))
viewHolder.name.text = item.toString()
viewHolder.address.text = item.address
return viewHolder.view
}
override fun getFilter(): Filter = ContactFilter()
private inner class ViewHolder(val view: View) {
val avatar = view.findViewById(R.id.avatar) as ImageView
val name = view.findViewById(R.id.name) as TextView
val address = view.findViewById(R.id.address) as TextView
}
private inner class ContactFilter : Filter() {
override fun performFiltering(prefix: CharSequence?): Filter.FilterResults {
val results = Filter.FilterResults()
if (prefix?.isEmpty() == false) {
val prefixString = prefix.toString().toLowerCase()
val newValues = ArrayList<BitmessageAddress>()
for (i in originalData.indices) {
val value = originalData[i]
// First match against the whole, non-splitted value
if (value.alias != null) {
val alias = value.alias!!.toLowerCase()
if (alias.startsWith(prefixString)) {
newValues.add(value)
} else {
val words = alias.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (word in words) {
if (word.startsWith(prefixString)) {
newValues.add(value)
break
}
}
}
} else {
val address = value.address.toLowerCase()
if (address.contains(prefixString)) {
newValues.add(value)
}
}
}
results.values = newValues
results.count = newValues.size
} else {
results.values = originalData
results.count = originalData.size
}
return results
}
override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) {
@Suppress("UNCHECKED_CAST")
data = results.values as List<BitmessageAddress>
if (results.count > 0) {
notifyDataSetChanged()
} else {
notifyDataSetInvalidated()
}
}
}
}

View File

@ -14,16 +14,11 @@
* limitations under the License.
*/
package ch.dissem.apps.abit.adapter;
package ch.dissem.apps.abit.adapter
/**
* @author Christian Basler
*/
class Selectable<T> {
final T data;
boolean selected = false;
Selectable(T data) {
this.data = data;
}
class Selectable<T>(val data: T) {
var selected = false
}

View File

@ -1,334 +0,0 @@
/*
* Copyright 2015 Haruki Hasegawa
* 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.adapter;
import android.annotation.SuppressLint;
import android.graphics.Typeface;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
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.SwipeResultActionRemoveItem;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
import com.h6ah4i.android.widget.advrecyclerview.utils.RecyclerViewAdapterUtils;
import java.util.LinkedList;
import java.util.List;
import ch.dissem.apps.abit.Identicon;
import ch.dissem.apps.abit.R;
import ch.dissem.apps.abit.util.Assets;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import static ch.dissem.apps.abit.repository.AndroidMessageRepository.LABEL_ARCHIVE;
import static ch.dissem.apps.abit.util.Strings.prepareMessageExtract;
/**
* Adapted from the basic swipeable example by Haruki Hasegawa. See
*
* @author Christian Basler
* @see <a href="https://github.com/h6ah4i/android-advancedrecyclerview">
* https://github.com/h6ah4i/android-advancedrecyclerview</a>
*/
public class SwipeableMessageAdapter
extends RecyclerView.Adapter<SwipeableMessageAdapter.ViewHolder>
implements SwipeableItemAdapter<SwipeableMessageAdapter.ViewHolder>, SwipeableItemConstants {
private List<Plaintext> data = new LinkedList<>();
private EventListener eventListener;
private final View.OnClickListener itemViewOnClickListener;
private final View.OnClickListener swipeableViewContainerOnClickListener;
private Label label;
private int selectedPosition = -1;
private boolean activateOnItemClick;
public void setActivateOnItemClick(boolean activateOnItemClick) {
this.activateOnItemClick = activateOnItemClick;
}
public interface EventListener {
void onItemDeleted(Plaintext item);
void onItemArchived(Plaintext item);
void onItemViewClicked(View v);
}
@SuppressWarnings("WeakerAccess")
static class ViewHolder extends AbstractSwipeableItemViewHolder {
public final FrameLayout container;
public final ImageView avatar;
public final ImageView status;
public final TextView sender;
public final TextView subject;
public final TextView extract;
ViewHolder(View v) {
super(v);
container = (FrameLayout) v.findViewById(R.id.container);
avatar = (ImageView) v.findViewById(R.id.avatar);
status = (ImageView) v.findViewById(R.id.status);
sender = (TextView) v.findViewById(R.id.sender);
subject = (TextView) v.findViewById(R.id.subject);
extract = (TextView) v.findViewById(R.id.text);
}
@Override
public View getSwipeableContainerView() {
return container;
}
}
public SwipeableMessageAdapter() {
itemViewOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemViewClick(view);
}
};
swipeableViewContainerOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
onSwipeableViewContainerClick(view);
}
};
// SwipeableItemAdapter requires stable ID, and also
// have to implement the getItemId() method appropriately.
setHasStableIds(true);
}
public void add(Plaintext item) {
data.add(item);
notifyDataSetChanged();
}
public void clear(Label newLabel) {
label = newLabel;
data.clear();
notifyDataSetChanged();
}
private void onItemViewClick(View v) {
if (eventListener != null) {
eventListener.onItemViewClicked(v);
}
}
private void onSwipeableViewContainerClick(View v) {
if (eventListener != null) {
eventListener.onItemViewClicked(
RecyclerViewAdapterUtils.getParentViewHolderItemView(v));
}
}
public Plaintext getItem(int position) {
return data.get(position);
}
@Override
public long getItemId(int position) {
return (long) data.get(position).getId();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final View v = inflater.inflate(R.layout.message_row, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final Plaintext item = data.get(position);
if (activateOnItemClick) {
holder.container.setBackgroundResource(
position == selectedPosition
? R.drawable.bg_item_selected_state
: R.drawable.bg_item_normal_state
);
}
// set listeners
// (if the item is *pinned*, click event comes to the itemView)
holder.itemView.setOnClickListener(itemViewOnClickListener);
// (if the item is *not pinned*, click event comes to the container)
holder.container.setOnClickListener(swipeableViewContainerOnClickListener);
// set data
holder.avatar.setImageDrawable(new Identicon(item.getFrom()));
holder.status.setImageResource(Assets.getStatusDrawable(item.getStatus()));
holder.status.setContentDescription(
holder.status.getContext().getString(Assets.getStatusString(item.getStatus())));
holder.sender.setText(item.getFrom().toString());
holder.subject.setText(prepareMessageExtract(item.getSubject()));
holder.extract.setText(prepareMessageExtract(item.getText()));
if (item.isUnread()) {
holder.sender.setTypeface(Typeface.DEFAULT_BOLD);
holder.subject.setTypeface(Typeface.DEFAULT_BOLD);
} else {
holder.sender.setTypeface(Typeface.DEFAULT);
holder.subject.setTypeface(Typeface.DEFAULT);
}
}
@Override
public int getItemCount() {
return data.size();
}
@Override
public int onGetSwipeReactionType(ViewHolder holder, int position, int x, int y) {
if (label == LABEL_ARCHIVE || label.getType() == Label.Type.TRASH) {
return REACTION_CAN_SWIPE_LEFT | REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT;
}
return REACTION_CAN_SWIPE_BOTH_H;
}
@Override
@SuppressLint("SwitchIntDef")
public void onSetSwipeBackground(ViewHolder holder, int position, int type) {
int bgRes = 0;
switch (type) {
case DRAWABLE_SWIPE_NEUTRAL_BACKGROUND:
bgRes = R.drawable.bg_swipe_item_neutral;
break;
case DRAWABLE_SWIPE_LEFT_BACKGROUND:
bgRes = R.drawable.bg_swipe_item_left;
break;
case DRAWABLE_SWIPE_RIGHT_BACKGROUND:
if (label == LABEL_ARCHIVE || label.getType() == Label.Type.TRASH) {
bgRes = R.drawable.bg_swipe_item_neutral;
} else {
bgRes = R.drawable.bg_swipe_item_right;
}
break;
}
holder.itemView.setBackgroundResource(bgRes);
}
@Override
@SuppressLint("SwitchIntDef")
public SwipeResultAction onSwipeItem(ViewHolder holder, final int position, int result) {
switch (result) {
// swipe right
case RESULT_SWIPED_RIGHT:
return new SwipeRightResultAction(this, position);
case RESULT_SWIPED_LEFT:
return new SwipeLeftResultAction(this, position);
// other --- do nothing
case RESULT_CANCELED:
default:
return null;
}
}
public void setEventListener(EventListener eventListener) {
this.eventListener = eventListener;
}
public void setSelectedPosition(int selectedPosition) {
int oldPosition = this.selectedPosition;
this.selectedPosition = selectedPosition;
notifyItemChanged(oldPosition);
notifyItemChanged(selectedPosition);
}
private static class SwipeLeftResultAction extends SwipeResultActionMoveToSwipedDirection {
private SwipeableMessageAdapter adapter;
private final int position;
private final Plaintext item;
SwipeLeftResultAction(SwipeableMessageAdapter adapter, int position) {
this.adapter = adapter;
this.position = position;
this.item = adapter.data.get(position);
}
@Override
protected void onPerformAction() {
super.onPerformAction();
adapter.data.remove(position);
adapter.notifyItemRemoved(position);
}
@Override
protected void onSlideAnimationEnd() {
super.onSlideAnimationEnd();
if (adapter.eventListener != null) {
adapter.eventListener.onItemDeleted(item);
}
}
@Override
protected void onCleanUp() {
super.onCleanUp();
// clear the references
adapter = null;
}
}
private static class SwipeRightResultAction extends SwipeResultActionRemoveItem {
private SwipeableMessageAdapter adapter;
private final int position;
private final Plaintext item;
SwipeRightResultAction(SwipeableMessageAdapter adapter, int position) {
this.adapter = adapter;
this.position = position;
this.item = adapter.data.get(position);
}
@Override
protected void onPerformAction() {
super.onPerformAction();
adapter.data.remove(position);
adapter.notifyItemRemoved(position);
}
@Override
protected void onSlideAnimationEnd() {
super.onSlideAnimationEnd();
if (adapter.eventListener != null) {
adapter.eventListener.onItemArchived(item);
}
}
@Override
protected void onCleanUp() {
super.onCleanUp();
// clear the references
adapter = null;
}
}
}

View File

@ -0,0 +1,251 @@
/*
* Copyright 2015 Haruki Hasegawa
* 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.adapter
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
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.SwipeResultActionRemoveItem
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder
import com.h6ah4i.android.widget.advrecyclerview.utils.RecyclerViewAdapterUtils
import java.util.LinkedList
import ch.dissem.apps.abit.Identicon
import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.util.Assets
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.apps.abit.repository.AndroidMessageRepository.Companion.LABEL_ARCHIVE
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
/**
* Adapted from the basic swipeable example by Haruki Hasegawa. See
*
* @author Christian Basler
* @see [
* https://github.com/h6ah4i/android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
*/
class SwipeableMessageAdapter : RecyclerView.Adapter<SwipeableMessageAdapter.ViewHolder>(), SwipeableItemAdapter<SwipeableMessageAdapter.ViewHolder>, SwipeableItemConstants {
private val data = LinkedList<Plaintext>()
var eventListener: EventListener? = null
private val itemViewOnClickListener: View.OnClickListener
private val swipeableViewContainerOnClickListener: View.OnClickListener
private var label: Label? = null
private var selectedPosition = -1
private var activateOnItemClick: Boolean = false
fun setActivateOnItemClick(activateOnItemClick: Boolean) {
this.activateOnItemClick = activateOnItemClick
}
interface EventListener {
fun onItemDeleted(item: Plaintext)
fun onItemArchived(item: Plaintext)
fun onItemViewClicked(v: View?)
}
class ViewHolder(v: View) : AbstractSwipeableItemViewHolder(v) {
val container = v.findViewById(R.id.container) as FrameLayout
val avatar = v.findViewById(R.id.avatar) as ImageView
val status = v.findViewById(R.id.status) as ImageView
val sender = v.findViewById(R.id.sender) as TextView
val subject = v.findViewById(R.id.subject) as TextView
val extract = v.findViewById(R.id.text) as TextView
override fun getSwipeableContainerView() = container
}
init {
itemViewOnClickListener = View.OnClickListener { view -> onItemViewClick(view) }
swipeableViewContainerOnClickListener = View.OnClickListener { view -> onSwipeableViewContainerClick(view) }
// SwipeableItemAdapter requires stable ID, and also
// have to implement the getItemId() method appropriately.
setHasStableIds(true)
}
fun add(item: Plaintext) {
data.add(item)
notifyDataSetChanged()
}
fun clear(newLabel: Label?) {
label = newLabel
data.clear()
notifyDataSetChanged()
}
private fun onItemViewClick(v: View) {
if (eventListener != null) {
eventListener!!.onItemViewClicked(v)
}
}
private fun onSwipeableViewContainerClick(v: View) {
if (eventListener != null) {
eventListener!!.onItemViewClicked(
RecyclerViewAdapterUtils.getParentViewHolderItemView(v))
}
}
fun getItem(position: Int) = data[position]
override fun getItemId(position: Int) = data[position].id as Long
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val v = inflater.inflate(R.layout.message_row, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
if (activateOnItemClick) {
holder.container.setBackgroundResource(
if (position == selectedPosition)
R.drawable.bg_item_selected_state
else
R.drawable.bg_item_normal_state
)
}
// set listeners
// (if the item is *pinned*, click event comes to the itemView)
holder.itemView.setOnClickListener(itemViewOnClickListener)
// (if the item is *not pinned*, click event comes to the container)
holder.container.setOnClickListener(swipeableViewContainerOnClickListener)
// set data
holder.avatar.setImageDrawable(Identicon(item.from))
holder.status.setImageResource(Assets.getStatusDrawable(item.status))
holder.status.contentDescription = holder.status.context.getString(Assets.getStatusString(item.status))
holder.sender.text = item.from.toString()
holder.subject.text = prepareMessageExtract(item.subject)
holder.extract.text = prepareMessageExtract(item.text)
if (item.isUnread()) {
holder.sender.typeface = Typeface.DEFAULT_BOLD
holder.subject.typeface = Typeface.DEFAULT_BOLD
} else {
holder.sender.typeface = Typeface.DEFAULT
holder.subject.typeface = Typeface.DEFAULT
}
}
override fun getItemCount() = data.size
override fun onGetSwipeReactionType(holder: ViewHolder, position: Int, x: Int, y: Int): Int {
return if (label === LABEL_ARCHIVE || label!!.type == Label.Type.TRASH) {
SwipeableItemConstants.REACTION_CAN_SWIPE_LEFT or SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT
} else SwipeableItemConstants.REACTION_CAN_SWIPE_BOTH_H
}
@SuppressLint("SwitchIntDef")
override fun onSetSwipeBackground(holder: ViewHolder, position: Int, type: Int) {
var bgRes = 0
when (type) {
SwipeableItemConstants.DRAWABLE_SWIPE_NEUTRAL_BACKGROUND -> bgRes = R.drawable.bg_swipe_item_neutral
SwipeableItemConstants.DRAWABLE_SWIPE_LEFT_BACKGROUND -> bgRes = R.drawable.bg_swipe_item_left
SwipeableItemConstants.DRAWABLE_SWIPE_RIGHT_BACKGROUND -> if (label === LABEL_ARCHIVE || label!!.type == Label.Type.TRASH) {
bgRes = R.drawable.bg_swipe_item_neutral
} else {
bgRes = R.drawable.bg_swipe_item_right
}
}
holder.itemView.setBackgroundResource(bgRes)
}
@SuppressLint("SwitchIntDef")
override fun onSwipeItem(holder: ViewHolder, position: Int, result: Int): SwipeResultAction? {
when (result) {
SwipeableItemConstants.RESULT_SWIPED_RIGHT -> return SwipeRightResultAction(this, position)
SwipeableItemConstants.RESULT_SWIPED_LEFT -> return SwipeLeftResultAction(this, position)
else -> return null
}
}
fun setSelectedPosition(selectedPosition: Int) {
val oldPosition = this.selectedPosition
this.selectedPosition = selectedPosition
notifyItemChanged(oldPosition)
notifyItemChanged(selectedPosition)
}
private class SwipeLeftResultAction internal constructor(adapter: SwipeableMessageAdapter, private val position: Int) : SwipeResultActionMoveToSwipedDirection() {
private var adapter: SwipeableMessageAdapter? = adapter
private val item = adapter.data[position]
override fun onPerformAction() {
super.onPerformAction()
adapter?.data?.removeAt(position)
adapter?.notifyItemRemoved(position)
}
override fun onSlideAnimationEnd() {
super.onSlideAnimationEnd()
adapter?.eventListener?.onItemDeleted(item)
}
override fun onCleanUp() {
super.onCleanUp()
// clear the references
adapter = null
}
}
private class SwipeRightResultAction internal constructor(adapter: SwipeableMessageAdapter, private val position: Int) : SwipeResultActionRemoveItem() {
private var adapter: SwipeableMessageAdapter? = adapter
private val item = adapter.data[position]
override fun onPerformAction() {
super.onPerformAction()
adapter?.data?.removeAt(position)
adapter?.notifyItemRemoved(position)
}
override fun onSlideAnimationEnd() {
super.onSlideAnimationEnd()
adapter?.eventListener?.onItemArchived(item)
}
override fun onCleanUp() {
super.onCleanUp()
// clear the references
adapter = null
}
}
}

View File

@ -1,65 +0,0 @@
/*
* 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.adapter;