Merge branch 'feature/kotlin' into develop
This commit is contained in:
commit
a23ae14f1d
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
@ -14,25 +14,25 @@
|
|||||||
* limitations under the License.
|
* 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
|
* An activity representing a single Subscription detail screen. This
|
||||||
* activity is only used on handset devices. On tablet-size devices,
|
* activity is only used on handset devices. On tablet-size devices,
|
||||||
* item details are presented side-by-side with a list of items
|
* item details are presented side-by-side with a list of items
|
||||||
* in a {@link MainActivity}.
|
* in a [MainActivity].
|
||||||
* <p/>
|
*
|
||||||
|
*
|
||||||
* This activity is mostly just a 'shell' activity containing nothing
|
* This activity is mostly just a 'shell' activity containing nothing
|
||||||
* more than a {@link AddressDetailFragment}.
|
* more than a [AddressDetailFragment].
|
||||||
*/
|
*/
|
||||||
public class AddressDetailActivity extends DetailActivity {
|
class AddressDetailActivity : DetailActivity() {
|
||||||
|
|
||||||
@Override
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
super.onCreate(savedInstanceState)
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
// savedInstanceState is non-null when there is fragment state
|
// savedInstanceState is non-null when there is fragment state
|
||||||
// saved from previous configurations of this activity
|
// saved from previous configurations of this activity
|
||||||
@ -42,18 +42,18 @@ public class AddressDetailActivity extends DetailActivity {
|
|||||||
// For more information, see the Fragments API guide at:
|
// For more information, see the Fragments API guide at:
|
||||||
//
|
//
|
||||||
// http://developer.android.com/guide/components/fragments.html
|
// http://developer.android.com/guide/components/fragments.html
|
||||||
//
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
// Create the detail fragment and add it to the activity
|
// Create the detail fragment and add it to the activity
|
||||||
// using a fragment transaction.
|
// using a fragment transaction.
|
||||||
Bundle arguments = new Bundle();
|
val arguments = Bundle()
|
||||||
arguments.putSerializable(AddressDetailFragment.ARG_ITEM,
|
arguments.putSerializable(AddressDetailFragment.ARG_ITEM,
|
||||||
getIntent().getSerializableExtra(AddressDetailFragment.ARG_ITEM));
|
intent.getSerializableExtra(AddressDetailFragment.ARG_ITEM))
|
||||||
AddressDetailFragment fragment = new AddressDetailFragment();
|
val fragment = AddressDetailFragment()
|
||||||
fragment.setArguments(arguments);
|
fragment.arguments = arguments
|
||||||
getSupportFragmentManager().beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.add(R.id.content, fragment)
|
.add(R.id.content, fragment)
|
||||||
.commit();
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
204
app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt
Normal file
204
app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* 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 ctx = activity
|
||||||
|
Drawables.addIcon(ctx, menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail)
|
||||||
|
Drawables.addIcon(ctx, menu, R.id.share, GoogleMaterial.Icon.gmd_share)
|
||||||
|
Drawables.addIcon(ctx, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete)
|
||||||
|
Drawables.addIcon(ctx, menu, R.id.export, CommunityMaterial.Icon.cmd_export).isVisible = 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)
|
||||||
|
if (item.privateKey != null) {
|
||||||
|
MainActivity.getInstance()?.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"
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
144
app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt
Normal file
144
app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt
Normal 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
|
||||||
|
)
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
100
app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt
Normal file
100
app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
220
app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.kt
Normal file
220
app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.kt
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
arguments?.let { arguments ->
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} ?: {
|
||||||
|
throw IllegalStateException("No identity set for ComposeMessageFragment")
|
||||||
|
}.invoke()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recipient?.let { recipient_input.setText(it.toString()) }
|
||||||
|
}
|
||||||
|
subject_input.setText(subject)
|
||||||
|
body_input.setText(content)
|
||||||
|
|
||||||
|
when {
|
||||||
|
recipient == null -> recipient_input.requestFocus()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
146
app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.kt
Normal file
146
app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.kt
Normal 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)
|
||||||
|
}
|
||||||
|
pubkeyBytes?.let { pubkeyBytes ->
|
||||||
|
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]+)=(.*)$")
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
46
app/src/main/java/ch/dissem/apps/abit/DetailActivity.kt
Normal file
46
app/src/main/java/ch/dissem/apps/abit/DetailActivity.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
98
app/src/main/java/ch/dissem/apps/abit/Identicon.kt
Normal file
98
app/src/main/java/ch/dissem/apps/abit/Identicon.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
102
app/src/main/java/ch/dissem/apps/abit/InputWifFragment.kt
Normal file
102
app/src/main/java/ch/dissem/apps/abit/InputWifFragment.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -14,17 +14,17 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.apps.abit;
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
public interface ListHolder<L> {
|
interface ListHolder<L> {
|
||||||
void updateList(L label);
|
fun updateList(label: L)
|
||||||
|
|
||||||
void setActivateOnItemClick(boolean activateOnItemClick);
|
fun setActivateOnItemClick(activateOnItemClick: Boolean)
|
||||||
|
|
||||||
L getCurrentLabel();
|
var currentLabel: L?
|
||||||
|
|
||||||
boolean showPreviousList();
|
fun showPreviousList(): Boolean
|
||||||
}
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
528
app/src/main/java/ch/dissem/apps/abit/MainActivity.kt
Normal file
528
app/src/main/java/ch/dissem/apps/abit/MainActivity.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
292
app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.kt
Normal file
292
app/src/main/java/ch/dissem/apps/abit/MessageDetailFragment.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
298
app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt
Normal file
298
app/src/main/java/ch/dissem/apps/abit/MessageListFragment.kt
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
* 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 && currentLabel != backStack.peek()) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,7 @@ import com.mikepenz.aboutlibraries.Libs
|
|||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
import org.jetbrains.anko.doAsync
|
import org.jetbrains.anko.doAsync
|
||||||
import org.jetbrains.anko.support.v4.indeterminateProgressDialog
|
import org.jetbrains.anko.support.v4.indeterminateProgressDialog
|
||||||
|
import org.jetbrains.anko.support.v4.startActivity
|
||||||
import org.jetbrains.anko.uiThread
|
import org.jetbrains.anko.uiThread
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
@ -59,13 +60,13 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
|
|
||||||
findPreference("about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
findPreference("about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
val libsBuilder = LibsBuilder()
|
val libsBuilder = LibsBuilder()
|
||||||
.withActivityTitle(activity.getString(R.string.about))
|
.withActivityTitle(activity.getString(R.string.about))
|
||||||
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
||||||
.withAboutIconShown(true)
|
.withAboutIconShown(true)
|
||||||
.withAboutVersionShown(true)
|
.withAboutVersionShown(true)
|
||||||
.withAboutDescription(getString(R.string.about_app))
|
.withAboutDescription(getString(R.string.about_app))
|
||||||
val activity = activity as MainActivity
|
val activity = activity as MainActivity
|
||||||
if (activity.hasDetailPane()) {
|
if (activity.hasDetailPane) {
|
||||||
activity.setDetailView(libsBuilder.supportFragment())
|
activity.setDetailView(libsBuilder.supportFragment())
|
||||||
} else {
|
} else {
|
||||||
libsBuilder.start(getActivity())
|
libsBuilder.start(getActivity())
|
||||||
@ -77,7 +78,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
val ctx = activity.applicationContext
|
val ctx = activity.applicationContext
|
||||||
cleanup.isEnabled = false
|
cleanup.isEnabled = false
|
||||||
Toast.makeText(ctx, R.string.cleanup_notification_start, Toast
|
Toast.makeText(ctx, R.string.cleanup_notification_start, Toast
|
||||||
.LENGTH_SHORT).show()
|
.LENGTH_SHORT).show()
|
||||||
|
|
||||||
doAsync {
|
doAsync {
|
||||||
val bmc = Singleton.getBitmessageContext(ctx)
|
val bmc = Singleton.getBitmessageContext(ctx)
|
||||||
@ -87,9 +88,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
|
|
||||||
uiThread {
|
uiThread {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
ctx,
|
ctx,
|
||||||
R.string.cleanup_notification_end,
|
R.string.cleanup_notification_end,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
cleanup.isEnabled = true
|
cleanup.isEnabled = true
|
||||||
}
|
}
|
||||||
@ -108,7 +109,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
val addressRepo = Singleton.getAddressRepository(context)
|
val addressRepo = Singleton.getAddressRepository(context)
|
||||||
val exportContacts = ContactExport.exportContacts(addressRepo.getContacts())
|
val exportContacts = ContactExport.exportContacts(addressRepo.getContacts())
|
||||||
zip.write(
|
zip.write(
|
||||||
exportContacts.toJsonString(true).toByteArray()
|
exportContacts.toJsonString(true).toByteArray()
|
||||||
)
|
)
|
||||||
zip.closeEntry()
|
zip.closeEntry()
|
||||||
|
|
||||||
@ -116,13 +117,13 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
zip.putNextEntry(ZipEntry("labels.json"))
|
zip.putNextEntry(ZipEntry("labels.json"))
|
||||||
val exportLabels = MessageExport.exportLabels(messageRepo.getLabels())
|
val exportLabels = MessageExport.exportLabels(messageRepo.getLabels())
|
||||||
zip.write(
|
zip.write(
|
||||||
exportLabels.toJsonString(true).toByteArray()
|
exportLabels.toJsonString(true).toByteArray()
|
||||||
)
|
)
|
||||||
zip.closeEntry()
|
zip.closeEntry()
|
||||||
zip.putNextEntry(ZipEntry("messages.json"))
|
zip.putNextEntry(ZipEntry("messages.json"))
|
||||||
val exportMessages = MessageExport.exportMessages(messageRepo.getAllMessages())
|
val exportMessages = MessageExport.exportMessages(messageRepo.getAllMessages())
|
||||||
zip.write(
|
zip.write(
|
||||||
exportMessages.toJsonString(true).toByteArray()
|
exportMessages.toJsonString(true).toByteArray()
|
||||||
)
|
)
|
||||||
zip.closeEntry()
|
zip.closeEntry()
|
||||||
}
|
}
|
||||||
@ -151,10 +152,10 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
|
|
||||||
findPreference("status").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
findPreference("status").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
val activity = activity as MainActivity
|
val activity = activity as MainActivity
|
||||||
if (activity.hasDetailPane()) {
|
if (activity.hasDetailPane) {
|
||||||
activity.setDetailView(StatusFragment())
|
activity.setDetailView(StatusFragment())
|
||||||
} else {
|
} else {
|
||||||
startActivity(Intent(getActivity(), StatusActivity::class.java))
|
startActivity<StatusActivity>()
|
||||||
}
|
}
|
||||||
return@OnPreferenceClickListener true
|
return@OnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
@ -217,7 +218,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
super.onAttach(ctx)
|
super.onAttach(ctx)
|
||||||
(ctx as? MainActivity)?.floatingActionButton?.hide()
|
(ctx as? MainActivity)?.floatingActionButton?.hide()
|
||||||
PreferenceManager.getDefaultSharedPreferences(ctx)
|
PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
.registerOnSharedPreferenceChangeListener(this)
|
.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
||||||
(ctx as? MainActivity)?.updateTitle(getString(R.string.settings))
|
(ctx as? MainActivity)?.updateTitle(getString(R.string.settings))
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
53
app/src/main/java/ch/dissem/apps/abit/StatusActivity.kt
Normal file
53
app/src/main/java/ch/dissem/apps/abit/StatusActivity.kt
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
46
app/src/main/java/ch/dissem/apps/abit/StatusFragment.kt
Normal file
46
app/src/main/java/ch/dissem/apps/abit/StatusFragment.kt
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -14,16 +14,16 @@
|
|||||||
* limitations under the License.
|
* 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.apps.abit.util.PRNGFixes
|
||||||
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography;
|
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
public class AndroidCryptography extends SpongyCryptography {
|
class AndroidCryptography : SpongyCryptography() {
|
||||||
public AndroidCryptography() {
|
init {
|
||||||
PRNGFixes.apply();
|
PRNGFixes.apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
132
app/src/main/java/ch/dissem/apps/abit/adapter/ContactAdapter.kt
Normal file
132
app/src/main/java/ch/dissem/apps/abit/adapter/ContactAdapter.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,16 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.apps.abit.adapter;
|
package ch.dissem.apps.abit.adapter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
class Selectable<T> {
|
class Selectable<T>(val data: T) {
|
||||||
final T data;
|
var selected = false
|
||||||
boolean selected = false;
|
|
||||||
|
|
||||||
Selectable(T data) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* 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 ch.dissem.apps.abit.Identicon
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidMessageRepository.Companion.LABEL_ARCHIVE
|
||||||
|
import ch.dissem.apps.abit.util.Assets
|
||||||
|
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
|
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter
|
||||||
|
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants
|
||||||
|
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants.*
|
||||||
|
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.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.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
eventListener?.onItemViewClicked(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSwipeableViewContainerClick(v: View) {
|
||||||
|
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]
|
||||||
|
|
||||||
|
holder.apply {
|
||||||
|
if (activateOnItemClick) {
|
||||||
|
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)
|
||||||
|
itemView.setOnClickListener(itemViewOnClickListener)
|
||||||
|
// (if the item is *not pinned*, click event comes to the container)
|
||||||
|
container.setOnClickListener(swipeableViewContainerOnClickListener)
|
||||||
|
|
||||||
|
// set data
|
||||||
|
avatar.setImageDrawable(Identicon(item.from))
|
||||||
|
status.setImageResource(Assets.getStatusDrawable(item.status))
|
||||||
|
status.contentDescription = holder.status.context.getString(Assets.getStatusString(item.status))
|
||||||
|
sender.text = item.from.toString()
|
||||||
|
subject.text = prepareMessageExtract(item.subject)
|
||||||
|
extract.text = prepareMessageExtract(item.text)
|
||||||
|
if (item.isUnread()) {
|
||||||
|
sender.typeface = Typeface.DEFAULT_BOLD
|
||||||
|
subject.typeface = Typeface.DEFAULT_BOLD
|
||||||
|
} else {
|
||||||
|
sender.typeface = Typeface.DEFAULT
|
||||||
|
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) {
|
||||||
|
REACTION_CAN_SWIPE_LEFT or REACTION_CAN_NOT_SWIPE_RIGHT_WITH_RUBBER_BAND_EFFECT
|
||||||
|
} else {
|
||||||
|
REACTION_CAN_SWIPE_BOTH_H
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SwitchIntDef")
|
||||||
|
override fun onSetSwipeBackground(holder: ViewHolder, position: Int, type: Int) {
|
||||||
|
var bgRes = 0
|
||||||
|
when (type) {
|
||||||
|
DRAWABLE_SWIPE_NEUTRAL_BACKGROUND -> bgRes = R.drawable.bg_swipe_item_neutral
|
||||||
|
DRAWABLE_SWIPE_LEFT_BACKGROUND -> bgRes = R.drawable.bg_swipe_item_left
|
||||||
|
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) =
|
||||||
|
when (result) {
|
||||||
|
RESULT_SWIPED_RIGHT -> SwipeRightResultAction(this, position)
|
||||||
|
RESULT_SWIPED_LEFT -> SwipeLeftResultAction(this, position)
|
||||||
|
else -> 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?.apply {
|
||||||
|
data.removeAt(position)
|
||||||
|
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?.apply {
|
||||||
|
data.removeAt(position)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSlideAnimationEnd() {
|
||||||
|
super.onSlideAnimationEnd()
|
||||||
|
adapter?.eventListener?.onItemArchived(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleanUp() {
|
||||||
|
super.onCleanUp()
|
||||||
|
// clear the references
|
||||||
|
adapter = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.InternalContext;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches between two {@link ProofOfWorkEngine}s depending on the configuration.
|
|
||||||
*
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class SwitchingProofOfWorkEngine implements ProofOfWorkEngine, InternalContext.ContextHolder {
|
|
||||||
private final Context ctx;
|
|
||||||
private final String preference;
|
|
||||||
private final ProofOfWorkEngine option;
|
|
||||||
private final ProofOfWorkEngine fallback;
|
|
||||||
|
|
||||||
public SwitchingProofOfWorkEngine(Context ctx, String preference,
|
|
||||||
ProofOfWorkEngine option, ProofOfWorkEngine fallback) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.preference = preference;
|
|
||||||
this.option = option;
|
|
||||||
this.fallback = fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
|
||||||
if (preferences.getBoolean(preference, false)) {
|
|
||||||
option.calculateNonce(initialHash, target, callback);
|
|
||||||
} else {
|
|
||||||
fallback.calculateNonce(initialHash, target, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext context) {
|
|
||||||
for (ProofOfWorkEngine e : Arrays.asList(option, fallback)) {
|
|
||||||
if (e instanceof InternalContext.ContextHolder) {
|
|
||||||
((InternalContext.ContextHolder) e).setContext(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.SharedPreferences
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches between two [ProofOfWorkEngine]s depending on the configuration.
|
||||||
|
*
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class SwitchingProofOfWorkEngine(
|
||||||
|
private val ctx: Context,
|
||||||
|
private val preference: String,
|
||||||
|
private val option: ProofOfWorkEngine,
|
||||||
|
private val fallback: ProofOfWorkEngine
|
||||||
|
) : ProofOfWorkEngine, InternalContext.ContextHolder {
|
||||||
|
|
||||||
|
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
if (preferences.getBoolean(preference, false)) {
|
||||||
|
option.calculateNonce(initialHash, target, callback)
|
||||||
|
} else {
|
||||||
|
fallback.calculateNonce(initialHash, target, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setContext(context: InternalContext) {
|
||||||
|
listOf(option, fallback)
|
||||||
|
.filterIsInstance<InternalContext.ContextHolder>()
|
||||||
|
.forEach { it.setContext(context) }
|
||||||
|
}
|
||||||
|
}
|
@ -1,162 +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.dialog;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
|
||||||
import android.support.v7.app.AppCompatDialogFragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.RadioGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.ImportIdentityActivity;
|
|
||||||
import ch.dissem.apps.abit.MainActivity;
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class AddIdentityDialogFragment extends AppCompatDialogFragment {
|
|
||||||
private BitmessageContext bmc;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
bmc = Singleton.getBitmessageContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
|
|
||||||
savedInstanceState) {
|
|
||||||
getDialog().setTitle(R.string.add_identity);
|
|
||||||
View view = inflater.inflate(R.layout.dialog_add_identity, container, false);
|
|
||||||
final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.radioGroup);
|
|
||||||
view.findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
final Context ctx = getActivity().getBaseContext();
|
|
||||||
switch (radioGroup.getCheckedRadioButtonId()) {
|
|
||||||
case R.id.create_identity:
|
|
||||||
Toast.makeText(ctx,
|
|
||||||
R.string.toast_long_running_operation,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
new AsyncTask<Void, Void, BitmessageAddress>() {
|
|
||||||
@Override
|
|
||||||
protected BitmessageAddress doInBackground(Void... args) {
|
|
||||||
return bmc.createIdentity(false, Pubkey.Feature.DOES_ACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(BitmessageAddress chan) {
|
|
||||||
Toast.makeText(ctx,
|
|
||||||
R.string.toast_identity_created,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
MainActivity mainActivity = MainActivity.getInstance();
|
|
||||||
if (mainActivity != null) {
|
|
||||||
mainActivity.addIdentityEntry(chan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
break;
|
|
||||||
case R.id.import_identity:
|
|
||||||
startActivity(new Intent(ctx, ImportIdentityActivity.class));
|
|
||||||
break;
|
|
||||||
case R.id.add_chan:
|
|
||||||
addChanDialog();
|
|
||||||
break;
|
|
||||||
case R.id.add_deterministic_address:
|
|
||||||
new DeterministicIdentityDialogFragment().show(getFragmentManager(),
|
|
||||||
"dialog");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
view.findViewById(R.id.dismiss).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addChanDialog() {
|
|
||||||
FragmentActivity activity = getActivity();
|
|
||||||
final Context ctx = activity.getBaseContext();
|
|
||||||
@SuppressLint("InflateParams")
|
|
||||||
final View dialogView = activity.getLayoutInflater()
|
|
||||||
.inflate(R.layout.dialog_input_passphrase, null);
|
|
||||||
new AlertDialog.Builder(activity)
|
|
||||||
.setTitle(R.string.add_chan)
|
|
||||||
.setView(dialogView)
|
|
||||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
TextView passphrase = (TextView) dialogView.findViewById(R.id.passphrase);
|
|
||||||
Toast.makeText(ctx, R.string.toast_long_running_operation,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
new AsyncTask<String, Void, BitmessageAddress>() {
|
|
||||||
@Override
|
|
||||||
protected BitmessageAddress doInBackground(String... args) {
|
|
||||||
String pass = args[0];
|
|
||||||
BitmessageAddress chan = bmc.createChan(pass);
|
|
||||||
chan.setAlias(pass);
|
|
||||||
bmc.addresses().save(chan);
|
|
||||||
return chan;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(BitmessageAddress chan) {
|
|
||||||
Toast.makeText(ctx,
|
|
||||||
R.string.toast_chan_created,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
MainActivity mainActivity = MainActivity.getInstance();
|
|
||||||
if (mainActivity != null) {
|
|
||||||
mainActivity.addIdentityEntry(chan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.execute(passphrase.getText().toString());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTheme() {
|
|
||||||
return R.style.FixedDialog;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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.dialog
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AppCompatDialogFragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import ch.dissem.apps.abit.ImportIdentityActivity
|
||||||
|
import ch.dissem.apps.abit.MainActivity
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import kotlinx.android.synthetic.main.dialog_add_identity.*
|
||||||
|
import org.jetbrains.anko.doAsync
|
||||||
|
import org.jetbrains.anko.support.v4.startActivity
|
||||||
|
import org.jetbrains.anko.uiThread
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
|
||||||
|
class AddIdentityDialogFragment : AppCompatDialogFragment() {
|
||||||
|
private lateinit var bmc: BitmessageContext
|
||||||
|
|
||||||
|
override fun onAttach(context: Context?) {
|
||||||
|
super.onAttach(context)
|
||||||
|
bmc = Singleton.getBitmessageContext(context!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
dialog.setTitle(R.string.add_identity)
|
||||||
|
return inflater.inflate(R.layout.dialog_add_identity, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
ok.setOnClickListener(View.OnClickListener {
|
||||||
|
val ctx = activity.baseContext
|
||||||
|
when (radioGroup.checkedRadioButtonId) {
|
||||||
|
R.id.create_identity -> {
|
||||||
|
Toast.makeText(ctx,
|
||||||
|
R.string.toast_long_running_operation,
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
doAsync {
|
||||||
|
val identity = bmc.createIdentity(false, Pubkey.Feature.DOES_ACK)
|
||||||
|
uiThread {
|
||||||
|
Toast.makeText(ctx,
|
||||||
|
R.string.toast_identity_created,
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
val mainActivity = MainActivity.getInstance()
|
||||||
|
mainActivity?.addIdentityEntry(identity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.import_identity -> startActivity<ImportIdentityActivity>()
|
||||||
|
R.id.add_chan -> addChanDialog()
|
||||||
|
R.id.add_deterministic_address -> DeterministicIdentityDialogFragment().show(fragmentManager, "dialog")
|
||||||
|
else -> return@OnClickListener
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
})
|
||||||
|
dismiss.setOnClickListener { dismiss() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addChanDialog() {
|
||||||
|
val activity = activity
|
||||||
|
val ctx = activity.baseContext
|
||||||
|
val dialogView = activity.layoutInflater.inflate(R.layout.dialog_input_passphrase, null)
|
||||||
|
AlertDialog.Builder(activity)
|
||||||
|
.setTitle(R.string.add_chan)
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
val passphrase = dialogView.findViewById(R.id.passphrase) as TextView
|
||||||
|
Toast.makeText(ctx, R.string.toast_long_running_operation,
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
val pass = passphrase.text.toString()
|
||||||
|
doAsync {
|
||||||
|
val chan = bmc.createChan(pass)
|
||||||
|
chan.alias = pass
|
||||||
|
bmc.addresses.save(chan)
|
||||||
|
uiThread {
|
||||||
|
Toast.makeText(ctx,
|
||||||
|
R.string.toast_chan_created,
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
MainActivity.getInstance()?.addIdentityEntry(chan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTheme() = R.style.FixedDialog
|
||||||
|
}
|
@ -1,136 +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.dialog;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v7.app.AppCompatDialogFragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Switch;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.MainActivity;
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class DeterministicIdentityDialogFragment extends AppCompatDialogFragment {
|
|
||||||
private BitmessageContext bmc;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
bmc = Singleton.getBitmessageContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
|
|
||||||
savedInstanceState) {
|
|
||||||
getDialog().setTitle(R.string.add_deterministic_address);
|
|
||||||
View view = inflater.inflate(R.layout.dialog_add_deterministic_identity, container, false);
|
|
||||||
view.findViewById(R.id.ok)
|
|
||||||
.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
dismiss();
|
|
||||||
final Context context = getActivity().getBaseContext();
|
|
||||||
View dialogView = getView();
|
|
||||||
assert dialogView != null;
|
|
||||||
TextView label = (TextView) dialogView.findViewById(R.id.label);
|
|
||||||
TextView passphrase = (TextView) dialogView.findViewById(R.id.passphrase);
|
|
||||||
TextView numberOfAddresses = (TextView) dialogView.findViewById(R.id
|
|
||||||
.number_of_identities);
|
|
||||||
Switch shorter = (Switch) dialogView.findViewById(R.id.shorter);
|
|
||||||
|
|
||||||
Toast.makeText(context, R.string.toast_long_running_operation,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
new AsyncTask<Object, Void, List<BitmessageAddress>>() {
|
|
||||||
@Override
|
|
||||||
protected List<BitmessageAddress> doInBackground(Object... args) {
|
|
||||||
String label = (String) args[0];
|
|
||||||
String pass = (String) args[1];
|
|
||||||
int numberOfAddresses = (int) args[2];
|
|
||||||
boolean shorter = (boolean) args[3];
|
|
||||||
List<BitmessageAddress> identities = bmc.createDeterministicAddresses
|
|
||||||
(pass,
|
|
||||||
numberOfAddresses, Pubkey.LATEST_VERSION, 1L, shorter);
|
|
||||||
int i = 0;
|
|
||||||
for (BitmessageAddress identity : identities) {
|
|
||||||
i++;
|
|
||||||
if (identities.size() == 1) {
|
|
||||||
identity.setAlias(label);
|
|
||||||
} else {
|
|
||||||
identity.setAlias(label + " (" + i + ")");
|
|
||||||
}
|
|
||||||
bmc.addresses().save(identity);
|
|
||||||
}
|
|
||||||
return identities;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(List<BitmessageAddress> identities) {
|
|
||||||
int messageRes;
|
|
||||||
if (identities.size() == 1) {
|
|
||||||
messageRes = R.string.toast_identity_created;
|
|
||||||
} else {
|
|
||||||
messageRes = R.string.toast_identities_created;
|
|
||||||
}
|
|
||||||
Toast.makeText(context,
|
|
||||||
messageRes,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
MainActivity mainActivity = MainActivity.getInstance();
|
|
||||||
if (mainActivity != null) {
|
|
||||||
for (BitmessageAddress identity : identities) {
|
|
||||||
mainActivity.addIdentityEntry(identity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.execute(
|
|
||||||
label.getText().toString(),
|
|
||||||
passphrase.getText().toString(),
|
|
||||||
Integer.valueOf(numberOfAddresses.getText().toString()),
|
|
||||||
shorter.isChecked()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
view.findViewById(R.id.dismiss).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTheme() {
|
|
||||||
return R.style.FixedDialog;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.dialog
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AppCompatDialogFragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import ch.dissem.apps.abit.MainActivity
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import kotlinx.android.synthetic.main.dialog_add_deterministic_identity.*
|
||||||
|
import org.jetbrains.anko.doAsync
|
||||||
|
import org.jetbrains.anko.uiThread
|
||||||
|
import kotlinx.android.synthetic.main.dialog_add_deterministic_identity.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class DeterministicIdentityDialogFragment : AppCompatDialogFragment() {
|
||||||
|
private lateinit var bmc: BitmessageContext
|
||||||
|
|
||||||
|
override fun onAttach(context: Context?) {
|
||||||
|
super.onAttach(context)
|
||||||
|
bmc = Singleton.getBitmessageContext(context!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
dialog.setTitle(R.string.add_deterministic_address)
|
||||||
|
return inflater.inflate(R.layout.dialog_add_deterministic_identity, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
ok.setOnClickListener {
|
||||||
|
dismiss()
|
||||||
|
val context = activity.baseContext
|
||||||
|
val dialogView = getView()!!
|
||||||
|
val passphrase = dialogView.findViewById(R.id.passphrase) as TextView
|
||||||
|
|
||||||
|
Toast.makeText(context, R.string.toast_long_running_operation,
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
doAsync {
|
||||||
|
val identities = bmc.createDeterministicAddresses(
|
||||||
|
passphrase.text.toString(),
|
||||||
|
number_of_identities.text.toString().toInt(),
|
||||||
|
Pubkey.LATEST_VERSION,
|
||||||
|
1L,
|
||||||
|
shorter.isChecked
|
||||||
|
)
|
||||||
|
for ((i, identity) in identities.withIndex()) {
|
||||||
|
if (identities.size == 1) {
|
||||||
|
identity.alias = label.text.toString()
|
||||||
|
} else {
|
||||||
|
identity.alias = "${label.text} (${i + 1})"
|
||||||
|
}
|
||||||
|
bmc.addresses.save(identity)
|
||||||
|
}
|
||||||
|
uiThread {
|
||||||
|
val messageRes = if (identities.size == 1) {
|
||||||
|
R.string.toast_identity_created
|
||||||
|
} else {
|
||||||
|
R.string.toast_identities_created
|
||||||
|
}
|
||||||
|
Toast.makeText(context,
|
||||||
|
messageRes,
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
MainActivity.getInstance()?.let { mainActivity ->
|
||||||
|
identities.forEach { identity ->
|
||||||
|
mainActivity.addIdentityEntry(identity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dismiss.setOnClickListener { dismiss() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTheme() = R.style.FixedDialog
|
||||||
|
}
|
@ -1,55 +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.dialog;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils;
|
|
||||||
import ch.dissem.apps.abit.util.Preferences;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class FullNodeDialogActivity extends Activity {
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.dialog_full_node);
|
|
||||||
findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
Preferences.setWifiOnly(FullNodeDialogActivity.this, false);
|
|
||||||
NetworkUtils.enableNode(getApplicationContext());
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
findViewById(R.id.dismiss).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
NetworkUtils.scheduleNodeStart(getApplicationContext());
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.dialog
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.util.NetworkUtils
|
||||||
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
|
import kotlinx.android.synthetic.main.dialog_full_node.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class FullNodeDialogActivity : Activity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.dialog_full_node)
|
||||||
|
ok.setOnClickListener {
|
||||||
|
Preferences.setWifiOnly(this@FullNodeDialogActivity, false)
|
||||||
|
NetworkUtils.enableNode(applicationContext)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
dismiss.setOnClickListener {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
NetworkUtils.scheduleNodeStart(applicationContext)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,98 +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.dialog;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.v7.app.AppCompatDialogFragment;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.RadioGroup;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
|
||||||
import static ch.dissem.apps.abit.ComposeMessageActivity.EXTRA_ENCODING;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED;
|
|
||||||
import static ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class SelectEncodingDialogFragment extends AppCompatDialogFragment {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SelectEncodingDialogFragment.class);
|
|
||||||
private Plaintext.Encoding encoding;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
if (getArguments() != null && getArguments().containsKey(EXTRA_ENCODING)) {
|
|
||||||
encoding = (Plaintext.Encoding) getArguments().getSerializable(EXTRA_ENCODING);
|
|
||||||
}
|
|
||||||
if (encoding == null) {
|
|
||||||
encoding = SIMPLE;
|
|
||||||
}
|
|
||||||
getDialog().setTitle(R.string.select_encoding_title);
|
|
||||||
View view = inflater.inflate(R.layout.dialog_select_message_encoding, container, false);
|
|
||||||
final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.radioGroup);
|
|
||||||
switch (encoding) {
|
|
||||||
case SIMPLE:
|
|
||||||
radioGroup.check(R.id.simple);
|
|
||||||
break;
|
|
||||||
case EXTENDED:
|
|
||||||
radioGroup.check(R.id.extended);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG.warn("Unexpected encoding: " + encoding);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
view.findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
switch (radioGroup.getCheckedRadioButtonId()) {
|
|
||||||
case R.id.extended:
|
|
||||||
encoding = EXTENDED;
|
|
||||||
break;
|
|
||||||
case R.id.simple:
|
|
||||||
encoding = SIMPLE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
dismiss();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Intent result = new Intent();
|
|
||||||
result.putExtra(EXTRA_ENCODING, encoding);
|
|
||||||
getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_OK, result);
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
view.findViewById(R.id.dismiss).setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.dialog
|
||||||
|
|
||||||
|
import android.app.Activity.RESULT_OK
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AppCompatDialogFragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_ENCODING
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE
|
||||||
|
import kotlinx.android.synthetic.main.dialog_select_message_encoding.*
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SelectEncodingDialogFragment : AppCompatDialogFragment() {
|
||||||
|
private lateinit var encoding: Plaintext.Encoding
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
encoding = (arguments.getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding) ?: SIMPLE
|
||||||
|
dialog.setTitle(R.string.select_encoding_title)
|
||||||
|
return inflater.inflate(R.layout.dialog_select_message_encoding, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
when (encoding) {
|
||||||
|
SIMPLE -> radioGroup.check(R.id.simple)
|
||||||
|
EXTENDED -> radioGroup.check(R.id.extended)
|
||||||
|
else -> LOG.warn("Unexpected encoding: " + encoding)
|
||||||
|
}
|
||||||
|
ok.setOnClickListener(View.OnClickListener {
|
||||||
|
encoding = when (radioGroup.checkedRadioButtonId) {
|
||||||
|
R.id.extended -> EXTENDED
|
||||||
|
R.id.simple -> SIMPLE
|
||||||
|
else -> {
|
||||||
|
dismiss()
|
||||||
|
return@OnClickListener
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val result = Intent()
|
||||||
|
result.putExtra(EXTRA_ENCODING, encoding)
|
||||||
|
targetFragment.onActivityResult(targetRequestCode, RESULT_OK, result)
|
||||||
|
dismiss()
|
||||||
|
})
|
||||||
|
dismiss.setOnClickListener { dismiss() }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(SelectEncodingDialogFragment::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -1,69 +0,0 @@
|
|||||||
package ch.dissem.apps.abit.drawer;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.view.Display;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
import com.mikepenz.materialdrawer.AccountHeader;
|
|
||||||
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.service.Singleton;
|
|
||||||
import ch.dissem.apps.abit.util.Drawables;
|
|
||||||
|
|
||||||
public class ProfileImageListener implements AccountHeader.OnAccountHeaderProfileImageListener {
|
|
||||||
private final Context ctx;
|
|
||||||
|
|
||||||
public ProfileImageListener(Context ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onProfileImageClick(View view, IProfile profile, boolean current) {
|
|
||||||
if (current) {
|
|
||||||
// Show QR code in modal dialog
|
|
||||||
final Dialog dialog = new Dialog(ctx);
|
|
||||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
|
||||||
|
|
||||||
ImageView imageView = new ImageView(ctx);
|
|
||||||
imageView.setImageBitmap(Drawables.qrCode(Singleton.getIdentity(ctx)));
|
|
||||||
imageView.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dialog.addContentView(imageView, new RelativeLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
Window window = dialog.getWindow();
|
|
||||||
if (window != null) {
|
|
||||||
Display display = window.getWindowManager().getDefaultDisplay();
|
|
||||||
Point size = new Point();
|
|
||||||
display.getSize(size);
|
|
||||||
int dim = size.x < size.y ? size.x : size.y;
|
|
||||||
|
|
||||||
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
|
|
||||||
lp.copyFrom(window.getAttributes());
|
|
||||||
lp.width = dim;
|
|
||||||
lp.height = dim;
|
|
||||||
|
|
||||||
window.setAttributes(lp);
|
|
||||||
}
|
|
||||||
dialog.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onProfileImageLongClick(View view, IProfile iProfile, boolean b) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,59 @@
|
|||||||
|
package ch.dissem.apps.abit.drawer
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.view.Display
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.Window
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
|
||||||
|
import com.mikepenz.materialdrawer.AccountHeader
|
||||||
|
import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
||||||
|
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.apps.abit.util.Drawables
|
||||||
|
|
||||||
|
class ProfileImageListener(private val ctx: Context) : AccountHeader.OnAccountHeaderProfileImageListener {
|
||||||
|
|
||||||
|
override fun onProfileImageClick(view: View, profile: IProfile<*>, current: Boolean): Boolean {
|
||||||
|
if (current) {
|
||||||
|
// Show QR code in modal dialog
|
||||||
|
val dialog = Dialog(ctx)
|
||||||
|
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||||
|
|
||||||
|
val imageView = ImageView(ctx)
|
||||||
|
imageView.setImageBitmap(Drawables.qrCode(Singleton.getIdentity(ctx)))
|
||||||
|
imageView.setOnClickListener { dialog.dismiss() }
|
||||||
|
dialog.addContentView(
|
||||||
|
imageView,
|
||||||
|
RelativeLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val window = dialog.window
|
||||||
|
if (window != null) {
|
||||||
|
val display = window.windowManager.defaultDisplay
|
||||||
|
val size = Point()
|
||||||
|
display.getSize(size)
|
||||||
|
val dim = if (size.x < size.y) size.x else size.y
|
||||||
|
|
||||||
|
val lp = WindowManager.LayoutParams()
|
||||||
|
lp.copyFrom(window.attributes)
|
||||||
|
lp.width = dim
|
||||||
|
lp.height = dim
|
||||||
|
|
||||||
|
window.attributes = lp
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProfileImageLongClick(view: View, iProfile: IProfile<*>, b: Boolean) = false
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
package ch.dissem.apps.abit.drawer;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.mikepenz.materialdrawer.AccountHeader;
|
|
||||||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
|
|
||||||
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.AddressDetailActivity;
|
|
||||||
import ch.dissem.apps.abit.AddressDetailFragment;
|
|
||||||
import ch.dissem.apps.abit.MainActivity;
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.apps.abit.dialog.AddIdentityDialogFragment;
|
|
||||||
import ch.dissem.apps.abit.service.Singleton;
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
|
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
|
||||||
|
|
||||||
public class ProfileSelectionListener implements AccountHeader.OnAccountHeaderListener {
|
|
||||||
private final Context ctx;
|
|
||||||
private final FragmentManager fragmentManager;
|
|
||||||
|
|
||||||
public ProfileSelectionListener(Context ctx, FragmentManager fragmentManager) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.fragmentManager = fragmentManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onProfileChanged(View view, IProfile profile, boolean current) {
|
|
||||||
switch ((int) profile.getIdentifier()) {
|
|
||||||
case MainActivity.ADD_IDENTITY:
|
|
||||||
addIdentityDialog();
|
|
||||||
break;
|
|
||||||
case MainActivity.MANAGE_IDENTITY:
|
|
||||||
BitmessageAddress identity = Singleton.getIdentity(ctx);
|
|
||||||
if (identity == null) {
|
|
||||||
Toast.makeText(ctx, R.string.no_identity_warning, LENGTH_LONG).show();
|
|
||||||
} else {
|
|
||||||
Intent show = new Intent(ctx, AddressDetailActivity.class);
|
|
||||||
show.putExtra(AddressDetailFragment.ARG_ITEM, identity);
|
|
||||||
ctx.startActivity(show);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (profile instanceof ProfileDrawerItem) {
|
|
||||||
Object tag = ((ProfileDrawerItem) profile).getTag();
|
|
||||||
if (tag instanceof BitmessageAddress) {
|
|
||||||
Singleton.setIdentity((BitmessageAddress) tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// false if it should close the drawer
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addIdentityDialog() {
|
|
||||||
AddIdentityDialogFragment dialog = new AddIdentityDialogFragment();
|
|
||||||
dialog.show(fragmentManager, "dialog");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,55 @@
|
|||||||
|
package ch.dissem.apps.abit.drawer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.support.v4.app.FragmentManager
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
|
||||||
|
import com.mikepenz.materialdrawer.AccountHeader
|
||||||
|
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
||||||
|
import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
||||||
|
|
||||||
|
import ch.dissem.apps.abit.AddressDetailActivity
|
||||||
|
import ch.dissem.apps.abit.AddressDetailFragment
|
||||||
|
import ch.dissem.apps.abit.MainActivity
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.dialog.AddIdentityDialogFragment
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
|
||||||
|
import android.widget.Toast.LENGTH_LONG
|
||||||
|
|
||||||
|
class ProfileSelectionListener(
|
||||||
|
private val ctx: Context,
|
||||||
|
private val fragmentManager: FragmentManager
|
||||||
|
) : AccountHeader.OnAccountHeaderListener {
|
||||||
|
|
||||||
|
override fun onProfileChanged(view: View, profile: IProfile<*>, current: Boolean): Boolean {
|
||||||
|
when (profile.identifier.toInt()) {
|
||||||
|
MainActivity.ADD_IDENTITY -> addIdentityDialog()
|
||||||
|
MainActivity.MANAGE_IDENTITY -> {
|
||||||
|
val identity = Singleton.getIdentity(ctx)
|
||||||
|
if (identity == null) {
|
||||||
|
Toast.makeText(ctx, R.string.no_identity_warning, LENGTH_LONG).show()
|
||||||
|
} else {
|
||||||
|
val show = Intent(ctx, AddressDetailActivity::class.java)
|
||||||
|
show.putExtra(AddressDetailFragment.ARG_ITEM, identity)
|
||||||
|
ctx.startActivity(show)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> if (profile is ProfileDrawerItem) {
|
||||||
|
val tag = profile.tag
|
||||||
|
if (tag is BitmessageAddress) {
|
||||||
|
Singleton.setIdentity(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// false if it should close the drawer
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addIdentityDialog() {
|
||||||
|
AddIdentityDialogFragment().show(fragmentManager, "dialog")
|
||||||
|
}
|
||||||
|
}
|
@ -14,16 +14,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.apps.abit.listener;
|
package ch.dissem.apps.abit.listener
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback interface that all activities containing this fragment must
|
* A callback interface that all activities containing this fragment must
|
||||||
* implement. This mechanism allows activities to be notified of item
|
* implement. This mechanism allows activities to be notified of item
|
||||||
* selections.
|
* selections.
|
||||||
*/
|
*/
|
||||||
public interface ListSelectionListener<T> {
|
interface ListSelectionListener<T> {
|
||||||
/**
|
/**
|
||||||
* Callback for when an item has been selected.
|
* Callback for when an item has been selected.
|
||||||
*/
|
*/
|
||||||
void onItemSelected(T item);
|
fun onItemSelected(item: T)
|
||||||
}
|
}
|
@ -1,85 +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.listener;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.MainActivity;
|
|
||||||
import ch.dissem.apps.abit.notification.NewMessageNotification;
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listens for decrypted Bitmessage messages. Does show a notification.
|
|
||||||
* <p>
|
|
||||||
* Should show a notification when the app isn't running, but update the message list when it is.
|
|
||||||
* Also,
|
|
||||||
* notifications should be combined.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class MessageListener implements BitmessageContext.Listener {
|
|
||||||
private final Deque<Plaintext> unacknowledged = new LinkedList<>();
|
|
||||||
private int numberOfUnacknowledgedMessages = 0;
|
|
||||||
private final NewMessageNotification notification;
|
|
||||||
private final ExecutorService pool = Executors.newSingleThreadExecutor();
|
|
||||||
|
|
||||||
public MessageListener(Context ctx) {
|
|
||||||
this.notification = new NewMessageNotification(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive(final Plaintext plaintext) {
|
|
||||||
pool.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
unacknowledged.addFirst(plaintext);
|
|
||||||
numberOfUnacknowledgedMessages++;
|
|
||||||
if (unacknowledged.size() > 5) {
|
|
||||||
unacknowledged.removeLast();
|
|
||||||
}
|
|
||||||
if (numberOfUnacknowledgedMessages == 1) {
|
|
||||||
notification.singleNotification(plaintext);
|
|
||||||
} else {
|
|
||||||
notification.multiNotification(unacknowledged, numberOfUnacknowledgedMessages);
|
|
||||||
}
|
|
||||||
notification.show();
|
|
||||||
|
|
||||||
// If MainActivity is shown, update the sidebar badges
|
|
||||||
MainActivity main = MainActivity.getInstance();
|
|
||||||
if (main != null) {
|
|
||||||
main.updateUnread();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetNotification() {
|
|
||||||
pool.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
notification.hide();
|
|
||||||
unacknowledged.clear();
|
|
||||||
numberOfUnacknowledgedMessages = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.listener
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
import java.util.Deque
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
import ch.dissem.apps.abit.MainActivity
|
||||||
|
import ch.dissem.apps.abit.notification.NewMessageNotification
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for decrypted Bitmessage messages. Does show a notification.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Should show a notification when the app isn't running, but update the message list when it is.
|
||||||
|
* Also,
|
||||||
|
* notifications should be combined.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MessageListener(ctx: Context) : BitmessageContext.Listener {
|
||||||
|
private val unacknowledged = LinkedList<Plaintext>()
|
||||||
|
private var numberOfUnacknowledgedMessages = 0
|
||||||
|
private val notification = NewMessageNotification(ctx)
|
||||||
|
private val pool = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
override fun receive(plaintext: Plaintext) {
|
||||||
|
pool.submit {
|
||||||
|
unacknowledged.addFirst(plaintext)
|
||||||
|
numberOfUnacknowledgedMessages++
|
||||||
|
if (unacknowledged.size > 5) {
|
||||||
|
unacknowledged.removeLast()
|
||||||
|
}
|
||||||
|
if (numberOfUnacknowledgedMessages == 1) {
|
||||||
|
notification.singleNotification(plaintext)
|
||||||
|
} else {
|
||||||
|
notification.multiNotification(unacknowledged, numberOfUnacknowledgedMessages)
|
||||||
|
}
|
||||||
|
notification.show()
|
||||||
|
|
||||||
|
// If MainActivity is shown, update the sidebar badges
|
||||||
|
MainActivity.getInstance()?.updateUnread()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetNotification() {
|
||||||
|
pool.submit {
|
||||||
|
notification.hide()
|
||||||
|
unacknowledged.clear()
|
||||||
|
numberOfUnacknowledgedMessages = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +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.listener;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.service.BitmessageService;
|
|
||||||
import ch.dissem.apps.abit.service.Singleton;
|
|
||||||
import ch.dissem.apps.abit.util.Preferences;
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
|
|
||||||
public class WifiReceiver extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
|
||||||
if ("android.net.conn.CONNECTIVITY_CHANGE".equals(intent.getAction())) {
|
|
||||||
BitmessageContext bmc = Singleton.getBitmessageContext(ctx);
|
|
||||||
if (Preferences.isWifiOnly(ctx) && isConnectedToMeteredNetwork(ctx) && bmc.isRunning()) {
|
|
||||||
bmc.shutdown();
|
|
||||||
}
|
|
||||||
if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && isConnectedToMeteredNetwork(ctx))) {
|
|
||||||
ctx.startService(new Intent(ctx, BitmessageService.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isConnectedToMeteredNetwork(Context ctx) {
|
|
||||||
NetworkInfo netInfo = getNetworkInfo(ctx);
|
|
||||||
if (netInfo == null || !netInfo.isConnectedOrConnecting()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (netInfo.getType()) {
|
|
||||||
case ConnectivityManager.TYPE_ETHERNET:
|
|
||||||
case ConnectivityManager.TYPE_WIFI:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NetworkInfo getNetworkInfo(Context ctx) {
|
|
||||||
ConnectivityManager conMan = (ConnectivityManager) ctx.getSystemService(Context
|
|
||||||
.CONNECTIVITY_SERVICE);
|
|
||||||
return conMan.getActiveNetworkInfo();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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.listener
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkInfo
|
||||||
|
import ch.dissem.apps.abit.service.BitmessageService
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
|
|
||||||
|
class WifiReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(ctx: Context, intent: Intent) {
|
||||||
|
if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) {
|
||||||
|
val bmc = Singleton.getBitmessageContext(ctx)
|
||||||
|
if (Preferences.isWifiOnly(ctx) && isConnectedToMeteredNetwork(ctx) && bmc.isRunning()) {
|
||||||
|
bmc.shutdown()
|
||||||
|
}
|
||||||
|
if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && isConnectedToMeteredNetwork(ctx))) {
|
||||||
|
ctx.startService(Intent(ctx, BitmessageService::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun isConnectedToMeteredNetwork(ctx: Context): Boolean {
|
||||||
|
val netInfo = getNetworkInfo(ctx)
|
||||||
|
if (netInfo == null || !netInfo.isConnectedOrConnecting) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
when (netInfo.type) {
|
||||||
|
ConnectivityManager.TYPE_ETHERNET, ConnectivityManager.TYPE_WIFI -> return false
|
||||||
|
else -> return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNetworkInfo(ctx: Context): NetworkInfo? {
|
||||||
|
val conMan = ctx.getSystemService(Context
|
||||||
|
.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
return conMan.activeNetworkInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,8 +35,8 @@ import ch.dissem.apps.abit.service.BitmessageIntentService
|
|||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import ch.dissem.apps.abit.MainActivity.EXTRA_REPLY_TO_MESSAGE
|
import ch.dissem.apps.abit.MainActivity.Companion.EXTRA_REPLY_TO_MESSAGE
|
||||||
import ch.dissem.apps.abit.MainActivity.EXTRA_SHOW_MESSAGE
|
import ch.dissem.apps.abit.MainActivity.Companion.EXTRA_SHOW_MESSAGE
|
||||||
import ch.dissem.apps.abit.service.BitmessageIntentService.Companion.EXTRA_DELETE_MESSAGE
|
import ch.dissem.apps.abit.service.BitmessageIntentService.Companion.EXTRA_DELETE_MESSAGE
|
||||||
import ch.dissem.apps.abit.util.Drawables.toBitmap
|
import ch.dissem.apps.abit.util.Drawables.toBitmap
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val ONGOING_NOTIFICATION_ID = 3
|
const val ONGOING_NOTIFICATION_ID = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(item: ProofOfWorkService.PowItem) {
|
fun start(item: ProofOfWorkService.PowItem) {
|
||||||
|
@ -1,97 +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.pow;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.service.Singleton;
|
|
||||||
import ch.dissem.apps.abit.synchronization.SyncAdapter;
|
|
||||||
import ch.dissem.apps.abit.util.Preferences;
|
|
||||||
import ch.dissem.bitmessage.InternalContext;
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
|
|
||||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class ServerPowEngine implements ProofOfWorkEngine, InternalContext
|
|
||||||
.ContextHolder {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ServerPowEngine.class);
|
|
||||||
|
|
||||||
private final Context ctx;
|
|
||||||
private InternalContext context;
|
|
||||||
|
|
||||||
private final ExecutorService pool;
|
|
||||||
|
|
||||||
public ServerPowEngine(Context ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
pool = Executors.newCachedThreadPool(new ThreadFactory() {
|
|
||||||
@Override
|
|
||||||
public Thread newThread(@NonNull Runnable r) {
|
|
||||||
Thread thread = Executors.defaultThreadFactory().newThread(r);
|
|
||||||
thread.setPriority(Thread.MIN_PRIORITY);
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void calculateNonce(final byte[] initialHash, final byte[] target, Callback callback) {
|
|
||||||
pool.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
BitmessageAddress identity = Singleton.getIdentity(ctx);
|
|
||||||
if (identity == null) throw new RuntimeException("No Identity for calculating POW");
|
|
||||||
|
|
||||||
ProofOfWorkRequest request = new ProofOfWorkRequest(identity, initialHash,
|
|
||||||
CALCULATE, target);
|
|
||||||
SyncAdapter.startPowSync(ctx);
|
|
||||||
try {
|
|
||||||
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>
|
|
||||||
(request);
|
|
||||||
cryptoMsg.signAndEncrypt(
|
|
||||||
identity,
|
|
||||||
cryptography().createPublicKey(identity.getPublicDecryptionKey())
|
|
||||||
);
|
|
||||||
context.getNetworkHandler().send(
|
|
||||||
Preferences.getTrustedNode(ctx), Preferences.getTrustedNodePort(ctx),
|
|
||||||
cryptoMsg);
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
}
|
|
92
app/src/main/java/ch/dissem/apps/abit/pow/ServerPowEngine.kt
Normal file
92
app/src/main/java/ch/dissem/apps/abit/pow/ServerPowEngine.kt
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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.pow
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
|
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
|
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
||||||
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
|
||||||
|
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class ServerPowEngine(private val ctx: Context) : ProofOfWorkEngine, InternalContext.ContextHolder {
|
||||||
|
private lateinit var context: InternalContext
|
||||||
|
|
||||||
|
private val pool: ExecutorService
|
||||||
|
|
||||||
|
init {
|
||||||
|
pool = Executors.newCachedThreadPool { r ->
|
||||||
|
val thread = Executors.defaultThreadFactory().newThread(r)
|
||||||
|
thread.priority = Thread.MIN_PRIORITY
|
||||||
|
thread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
|
||||||
|
pool.execute {
|
||||||
|
val identity = Singleton.getIdentity(ctx) ?: throw RuntimeException("No Identity for calculating POW")
|
||||||
|
|
||||||
|
val request = ProofOfWorkRequest(identity, initialHash,
|
||||||
|
CALCULATE, target)
|
||||||
|
SyncAdapter.startPowSync(ctx)
|
||||||
|
try {
|
||||||
|
val cryptoMsg = CryptoCustomMessage(request)
|
||||||
|
cryptoMsg.signAndEncrypt(
|
||||||
|
identity,
|
||||||
|
cryptography().createPublicKey(identity.publicDecryptionKey)
|
||||||
|
)
|
||||||
|
val node = Preferences.getTrustedNode(ctx)
|
||||||
|
if (node == null) {
|
||||||
|
LOG.error("trusted node is not defined")
|
||||||
|
} else {
|
||||||
|
context.networkHandler.send(
|
||||||
|
node,
|
||||||
|
Preferences.getTrustedNodePort(ctx),
|
||||||
|
cryptoMsg)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.error(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setContext(context: InternalContext) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(ServerPowEngine::class.java)
|
||||||
|
}
|
||||||
|
}
|
@ -1,312 +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.repository;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link AddressRepository} implementation using the Android SQL API.
|
|
||||||
*/
|
|
||||||
public class AndroidAddressRepository implements AddressRepository {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AndroidAddressRepository.class);
|
|
||||||
|
|
||||||
private static final String TABLE_NAME = "Address";
|
|
||||||
private static final String COLUMN_ADDRESS = "address";
|
|
||||||
private static final String COLUMN_VERSION = "version";
|
|
||||||
private static final String COLUMN_ALIAS = "alias";
|
|
||||||
private static final String COLUMN_PUBLIC_KEY = "public_key";
|
|
||||||
private static final String COLUMN_PRIVATE_KEY = "private_key";
|
|
||||||
private static final String COLUMN_SUBSCRIBED = "subscribed";
|
|
||||||
private static final String COLUMN_CHAN = "chan";
|
|
||||||
|
|
||||||
private final SqlHelper sql;
|
|
||||||
|
|
||||||
public AndroidAddressRepository(SqlHelper sql) {
|
|
||||||
this.sql = sql;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BitmessageAddress findContact(byte[] ripeOrTag) {
|
|
||||||
for (BitmessageAddress address : find("public_key is null")) {
|
|
||||||
if (address.getVersion() > 3) {
|
|
||||||
if (Arrays.equals(ripeOrTag, address.getTag())) return address;
|
|
||||||
} else {
|
|
||||||
if (Arrays.equals(ripeOrTag, address.getRipe())) return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BitmessageAddress findIdentity(byte[] ripeOrTag) {
|
|
||||||
for (BitmessageAddress address : find("private_key is not null")) {
|
|
||||||
if (address.getVersion() > 3) {
|
|
||||||
if (Arrays.equals(ripeOrTag, address.getTag())) return address;
|
|
||||||
} else {
|
|
||||||
if (Arrays.equals(ripeOrTag, address.getRipe())) return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<BitmessageAddress> getIdentities() {
|
|
||||||
return find("private_key IS NOT NULL");
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<BitmessageAddress> getChans() {
|
|
||||||
return find("chan = '1'");
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<BitmessageAddress> getSubscriptions() {
|
|
||||||
return find("subscribed = '1'");
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<BitmessageAddress> getSubscriptions(long broadcastVersion) {
|
|
||||||
if (broadcastVersion > 4) {
|
|
||||||
return find("subscribed = '1' AND version > 3");
|
|
||||||
} else {
|
|
||||||
return find("subscribed = '1' AND version <= 3");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<BitmessageAddress> getContacts() {
|
|
||||||
return find("private_key IS NULL OR chan = '1'");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the contacts in the following order:
|
|
||||||
* <ul>
|
|
||||||
* <li>Subscribed addresses come first
|
|
||||||
* <li>Addresses with Aliases (alphabetically)
|
|
||||||
* <li>Addresses (alphabetically)
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @return the ordered list of ids (address strings)
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public List<String> getContactIds() {
|
|
||||||
return findIds(
|
|
||||||
"private_key IS NULL OR chan = '1'",
|
|
||||||
COLUMN_SUBSCRIBED + " DESC, " + COLUMN_ALIAS + " IS NULL, " + COLUMN_ALIAS + ", " + COLUMN_ADDRESS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private List<String> findIds(String where, String orderBy) {
|
|
||||||
List<String> result = new LinkedList<>();
|
|
||||||
|
|
||||||
// Define a projection that specifies which columns from the database
|
|
||||||
// you will actually use after this query.
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_ADDRESS
|
|
||||||
};
|
|
||||||
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
where,
|
|
||||||
null, null, null,
|
|
||||||
orderBy
|
|
||||||
)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
result.add(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private List<BitmessageAddress> find(String where) {
|
|
||||||
List<BitmessageAddress> result = new LinkedList<>();
|
|
||||||
|
|
||||||
// Define a projection that specifies which columns from the database
|
|
||||||
// you will actually use after this query.
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_ADDRESS,
|
|
||||||
COLUMN_ALIAS,
|
|
||||||
COLUMN_PUBLIC_KEY,
|
|
||||||
COLUMN_PRIVATE_KEY,
|
|
||||||
COLUMN_SUBSCRIBED,
|
|
||||||
COLUMN_CHAN
|
|
||||||
};
|
|
||||||
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
where,
|
|
||||||
null, null, null, null
|
|
||||||
)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
BitmessageAddress address;
|
|
||||||
|
|
||||||
byte[] privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY));
|
|
||||||
if (privateKeyBytes != null) {
|
|
||||||
PrivateKey privateKey = PrivateKey.read(new ByteArrayInputStream
|
|
||||||
(privateKeyBytes));
|
|
||||||
address = new BitmessageAddress(privateKey);
|
|
||||||
} else {
|
|
||||||
address = new BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)));
|
|
||||||
byte[] publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY));
|
|
||||||
if (publicKeyBytes != null) {
|
|
||||||
Pubkey pubkey = Factory.readPubkey(address.getVersion(), address
|
|
||||||
.getStream(),
|
|
||||||
new ByteArrayInputStream(publicKeyBytes), publicKeyBytes.length,
|
|
||||||
false);
|
|
||||||
if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) {
|
|
||||||
pubkey = new V4Pubkey((V3Pubkey) pubkey);
|
|
||||||
}
|
|
||||||
address.setPubkey(pubkey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
address.setAlias(c.getString(c.getColumnIndex(COLUMN_ALIAS)));
|
|
||||||
address.setChan(c.getInt(c.getColumnIndex(COLUMN_CHAN)) == 1);
|
|
||||||
address.setSubscribed(c.getInt(c.getColumnIndex(COLUMN_SUBSCRIBED)) == 1);
|
|
||||||
|
|
||||||
result.add(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void save(BitmessageAddress address) {
|
|
||||||
if (exists(address)) {
|
|
||||||
update(address);
|
|
||||||
} else {
|
|
||||||
insert(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean exists(BitmessageAddress address) {
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
try (Cursor cursor = db.rawQuery(
|
|
||||||
"SELECT COUNT(*) FROM Address WHERE address=?",
|
|
||||||
new String[]{address.getAddress()}
|
|
||||||
)) {
|
|
||||||
cursor.moveToFirst();
|
|
||||||
return cursor.getInt(0) > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update(BitmessageAddress address) {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
// Create a new map of values, where column names are the keys
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
if (address.getAlias() != null) {
|
|
||||||
values.put(COLUMN_ALIAS, address.getAlias());
|
|
||||||
}
|
|
||||||
if (address.getPubkey() != null) {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
address.getPubkey().writeUnencrypted(out);
|
|
||||||
values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
|
|
||||||
}
|
|
||||||
if (address.getPrivateKey() != null) {
|
|
||||||
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
|
|
||||||
}
|
|
||||||
if (address.isChan()) {
|
|
||||||
values.put(COLUMN_CHAN, true);
|
|
||||||
}
|
|
||||||
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
|
|
||||||
|
|
||||||
int update = db.update(TABLE_NAME, values, "address=?",
|
|
||||||
new String[]{address.getAddress()});
|
|
||||||
if (update < 0) {
|
|
||||||
LOG.error("Could not update address " + address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insert(BitmessageAddress address) {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
// Create a new map of values, where column names are the keys
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(COLUMN_ADDRESS, address.getAddress());
|
|
||||||
values.put(COLUMN_VERSION, address.getVersion());
|
|
||||||
values.put(COLUMN_ALIAS, address.getAlias());
|
|
||||||
if (address.getPubkey() != null) {
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
address.getPubkey().writeUnencrypted(out);
|
|
||||||
values.put(COLUMN_PUBLIC_KEY, out.toByteArray());
|
|
||||||
} else {
|
|
||||||
values.put(COLUMN_PUBLIC_KEY, (byte[]) null);
|
|
||||||
}
|
|
||||||
if (address.getPrivateKey() != null) {
|
|
||||||
values.put(COLUMN_PRIVATE_KEY, Encode.bytes(address.getPrivateKey()));
|
|
||||||
}
|
|
||||||
values.put(COLUMN_CHAN, address.isChan());
|
|
||||||
values.put(COLUMN_SUBSCRIBED, address.isSubscribed());
|
|
||||||
|
|
||||||
long insert = db.insert(TABLE_NAME, null, values);
|
|
||||||
if (insert < 0) {
|
|
||||||
LOG.error("Could not insert address " + address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(BitmessageAddress address) {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
db.delete(TABLE_NAME, "address = ?", new String[]{address.getAddress()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public BitmessageAddress getById(String id) {
|
|
||||||
List<BitmessageAddress> result = find("address = '" + id + "'");
|
|
||||||
if (result.size() > 0) {
|
|
||||||
return result.get(0);
|
|
||||||
} else {
|
|
||||||
throw new ApplicationException("Address with id " + id + " not found.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BitmessageAddress getAddress(String address) {
|
|
||||||
List<BitmessageAddress> result = find("address = '" + address + "'");
|
|
||||||
if (result.size() > 0) return result.get(0);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* 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.repository
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.payload.V3Pubkey
|
||||||
|
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.ports.AddressRepository
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [AddressRepository] implementation using the Android SQL API.
|
||||||
|
*/
|
||||||
|
class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
|
||||||
|
|
||||||
|
override fun findContact(ripeOrTag: ByteArray): BitmessageAddress? {
|
||||||
|
for (address in find("public_key is null")) {
|
||||||
|
if (address.version > 3) {
|
||||||
|
if (Arrays.equals(ripeOrTag, address.tag)) return address
|
||||||
|
} else {
|
||||||
|
if (Arrays.equals(ripeOrTag, address.ripe)) return address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findIdentity(ripeOrTag: ByteArray): BitmessageAddress? {
|
||||||
|
for (address in find("private_key is not null")) {
|
||||||
|
if (address.version > 3) {
|
||||||
|
if (Arrays.equals(ripeOrTag, address.tag)) return address
|
||||||
|
} else {
|
||||||
|
if (Arrays.equals(ripeOrTag, address.ripe)) return address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIdentities() = find("private_key IS NOT NULL")
|
||||||
|
|
||||||
|
override fun getChans() = find("chan = '1'")
|
||||||
|
|
||||||
|
override fun getSubscriptions() = find("subscribed = '1'")
|
||||||
|
|
||||||
|
override fun getSubscriptions(broadcastVersion: Long) = if (broadcastVersion > 4) {
|
||||||
|
find("subscribed = '1' AND version > 3")
|
||||||
|
} else {
|
||||||
|
find("subscribed = '1' AND version <= 3")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getContacts() = find("private_key IS NULL OR chan = '1'")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contacts in the following order:
|
||||||
|
*
|
||||||
|
* * Subscribed addresses come first
|
||||||
|
* * Addresses with Aliases (alphabetically)
|
||||||
|
* * Addresses (alphabetically)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return the ordered list of ids (address strings)
|
||||||
|
*/
|
||||||
|
fun getContactIds(): List<String> = findIds(
|
||||||
|
"private_key IS NULL OR chan = '1'",
|
||||||
|
"$COLUMN_SUBSCRIBED DESC, $COLUMN_ALIAS IS NULL, $COLUMN_ALIAS, $COLUMN_ADDRESS"
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun findIds(where: String, orderBy: String): List<String> {
|
||||||
|
val result = LinkedList<String>()
|
||||||
|
|
||||||
|
// Define a projection that specifies which columns from the database
|
||||||
|
// you will actually use after this query.
|
||||||
|
val projection = arrayOf(COLUMN_ADDRESS)
|
||||||
|
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
db.query(
|
||||||
|
TABLE_NAME, projection,
|
||||||
|
where, null, null, null,
|
||||||
|
orderBy
|
||||||
|
).use { c ->
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
result.add(c.getString(c.getColumnIndex(COLUMN_ADDRESS)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun find(where: String): List<BitmessageAddress> {
|
||||||
|
val result = LinkedList<BitmessageAddress>()
|
||||||
|
|
||||||
|
// Define a projection that specifies which columns from the database
|
||||||
|
// you will actually use after this query.
|
||||||
|
val projection = arrayOf(COLUMN_ADDRESS, COLUMN_ALIAS, COLUMN_PUBLIC_KEY, COLUMN_PRIVATE_KEY, COLUMN_SUBSCRIBED, COLUMN_CHAN)
|
||||||
|
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
db.query(
|
||||||
|
TABLE_NAME, projection,
|
||||||
|
where, null, null, null, null
|
||||||
|
).use { c ->
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
val address: BitmessageAddress
|
||||||
|
|
||||||
|
val privateKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PRIVATE_KEY))
|
||||||
|
if (privateKeyBytes != null) {
|
||||||
|
val privateKey = PrivateKey.read(ByteArrayInputStream(privateKeyBytes))
|
||||||
|
address = BitmessageAddress(privateKey)
|
||||||
|
} else {
|
||||||
|
address = BitmessageAddress(c.getString(c.getColumnIndex(COLUMN_ADDRESS)))
|
||||||
|
val publicKeyBytes = c.getBlob(c.getColumnIndex(COLUMN_PUBLIC_KEY))
|
||||||
|
if (publicKeyBytes != null) {
|
||||||
|
var pubkey = Factory.readPubkey(address.version, address
|
||||||
|
.stream,
|
||||||
|
ByteArrayInputStream(publicKeyBytes), publicKeyBytes.size,
|
||||||
|
false)
|
||||||
|
if (address.version == 4L && pubkey is V3Pubkey) {
|
||||||
|
pubkey = V4Pubkey(pubkey)
|
||||||
|
}
|
||||||
|
address.pubkey = pubkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
address.alias = c.getString(c.getColumnIndex(COLUMN_ALIAS))
|
||||||
|
address.isChan = c.getInt(c.getColumnIndex(COLUMN_CHAN)) == 1
|
||||||
|
address.isSubscribed = c.getInt(c.getColumnIndex(COLUMN_SUBSCRIBED)) == 1
|
||||||
|
|
||||||
|
result.add(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun save(address: BitmessageAddress) {
|
||||||
|
if (exists(address)) {
|
||||||
|
update(address)
|
||||||
|
} else {
|
||||||
|
insert(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exists(address: BitmessageAddress): Boolean {
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
db.rawQuery(
|
||||||
|
"SELECT COUNT(*) FROM Address WHERE address=?",
|
||||||
|
arrayOf(address.address)
|
||||||
|
).use { cursor ->
|
||||||
|
cursor.moveToFirst()
|
||||||
|
return cursor.getInt(0) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(address: BitmessageAddress) {
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
// Create a new map of values, where column names are the keys
|
||||||
|
val values = ContentValues()
|
||||||
|
address.alias?.let { values.put(COLUMN_ALIAS, it) }
|
||||||
|
address.pubkey?.let { pubkey ->
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
pubkey.writeUnencrypted(out)
|
||||||
|
values.put(COLUMN_PUBLIC_KEY, out.toByteArray())
|
||||||
|
}
|
||||||
|
address.privateKey?.let { values.put(COLUMN_PRIVATE_KEY, Encode.bytes(it)) }
|
||||||
|
if (address.isChan) {
|
||||||
|
values.put(COLUMN_CHAN, true)
|
||||||
|
}
|
||||||
|
values.put(COLUMN_SUBSCRIBED, address.isSubscribed)
|
||||||
|
|
||||||
|
val update = db.update(TABLE_NAME, values, "address=?", arrayOf(address.address))
|
||||||
|
if (update < 0) {
|
||||||
|
LOG.error("Could not update address {}", address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insert(address: BitmessageAddress) {
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
// Create a new map of values, where column names are the keys
|
||||||
|
val values = ContentValues()
|
||||||
|
values.put(COLUMN_ADDRESS, address.address)
|
||||||
|
values.put(COLUMN_VERSION, address.version)
|
||||||
|
values.put(COLUMN_ALIAS, address.alias)
|
||||||
|
address.pubkey?.let { pubkey ->
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
pubkey.writeUnencrypted(out)
|
||||||
|
values.put(COLUMN_PUBLIC_KEY, out.toByteArray())
|
||||||
|
} ?: {
|
||||||
|
values.put(COLUMN_PUBLIC_KEY, null as ByteArray?)
|
||||||
|
}.invoke()
|
||||||
|
address.privateKey?.let { values.put(COLUMN_PRIVATE_KEY, Encode.bytes(it)) }
|
||||||
|
values.put(COLUMN_CHAN, address.isChan)
|
||||||
|
values.put(COLUMN_SUBSCRIBED, address.isSubscribed)
|
||||||
|
|
||||||
|
val insert = db.insert(TABLE_NAME, null, values)
|
||||||
|
if (insert < 0) {
|
||||||
|
LOG.error("Could not insert address {}", address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(address: BitmessageAddress) {
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
db.delete(TABLE_NAME, "address = ?", arrayOf(address.address))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getById(id: String): BitmessageAddress {
|
||||||
|
val result = find("address = '$id'")
|
||||||
|
return if (result.isNotEmpty()) {
|
||||||
|
result[0]
|
||||||
|
} else {
|
||||||
|
throw ApplicationException("Address with id $id not found.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAddress(address: String) = find("address = '$address'").firstOrNull()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(AndroidAddressRepository::class.java)
|
||||||
|
|
||||||
|
private const val TABLE_NAME = "Address"
|
||||||
|
private const val COLUMN_ADDRESS = "address"
|
||||||
|
private const val COLUMN_VERSION = "version"
|
||||||
|
private const val COLUMN_ALIAS = "alias"
|
||||||
|
private const val COLUMN_PUBLIC_KEY = "public_key"
|
||||||
|
private const val COLUMN_PRIVATE_KEY = "private_key"
|
||||||
|
private const val COLUMN_SUBSCRIBED = "subscribed"
|
||||||
|
private const val COLUMN_CHAN = "chan"
|
||||||
|
}
|
||||||
|
}
|
@ -1,232 +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.repository;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteConstraintException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.ports.Inventory;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import static ch.dissem.apps.abit.repository.SqlHelper.join;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.now;
|
|
||||||
import static java.lang.String.valueOf;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Inventory} implementation using the Android SQL API.
|
|
||||||
*/
|
|
||||||
public class AndroidInventory implements Inventory {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AndroidInventory.class);
|
|
||||||
|
|
||||||
private static final String TABLE_NAME = "Inventory";
|
|
||||||
private static final String COLUMN_HASH = "hash";
|
|
||||||
private static final String COLUMN_STREAM = "stream";
|
|
||||||
private static final String COLUMN_EXPIRES = "expires";
|
|
||||||
private static final String COLUMN_DATA = "data";
|
|
||||||
private static final String COLUMN_TYPE = "type";
|
|
||||||
private static final String COLUMN_VERSION = "version";
|
|
||||||
|
|
||||||
private final SqlHelper sql;
|
|
||||||
|
|
||||||
private final Map<Long, Map<InventoryVector, Long>> cache = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public AndroidInventory(SqlHelper sql) {
|
|
||||||
this.sql = sql;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<InventoryVector> getInventory(long... streams) {
|
|
||||||
List<InventoryVector> result = new LinkedList<>();
|
|
||||||
long now = now();
|
|
||||||
for (long stream : streams) {
|
|
||||||
for (Map.Entry<InventoryVector, Long> e : getCache(stream).entrySet()) {
|
|
||||||
if (e.getValue() > now) {
|
|
||||||
result.add(e.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<InventoryVector, Long> getCache(long stream) {
|
|
||||||
Map<InventoryVector, Long> result = cache.get(stream);
|
|
||||||
if (result == null) {
|
|
||||||
synchronized (cache) {
|
|
||||||
if (cache.get(stream) == null) {
|
|
||||||
result = new ConcurrentHashMap<>();
|
|
||||||
cache.put(stream, result);
|
|
||||||
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_HASH, COLUMN_EXPIRES
|
|
||||||
};
|
|
||||||
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
"stream = " + stream,
|
|
||||||
null, null, null, null
|
|
||||||
)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_HASH));
|
|
||||||
long expires = c.getLong(c.getColumnIndex(COLUMN_EXPIRES));
|
|
||||||
result.put(InventoryVector.fromHash(blob), expires);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.info("Stream #" + stream + " inventory size: " + result.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams) {
|
|
||||||
for (long stream : streams) {
|
|
||||||
offer.removeAll(getCache(stream).keySet());
|
|
||||||
}
|
|
||||||
LOG.info(offer.size() + " objects missing.");
|
|
||||||
return offer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ObjectMessage getObject(InventoryVector vector) {
|
|
||||||
// Define a projection that specifies which columns from the database
|
|
||||||
// you will actually use after this query.
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_VERSION,
|
|
||||||
COLUMN_DATA
|
|
||||||
};
|
|
||||||
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
"hash = X'" + vector + "'",
|
|
||||||
null, null, null, null
|
|
||||||
)) {
|
|
||||||
if (!c.moveToFirst()) {
|
|
||||||
LOG.info("Object requested that we don't have. IV: " + vector);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
|
||||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
|
||||||
return Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ObjectMessage> getObjects(long stream, long version, ObjectType... types) {
|
|
||||||
// Define a projection that specifies which columns from the database
|
|
||||||
// you will actually use after this query.
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_VERSION,
|
|
||||||
COLUMN_DATA
|
|
||||||
};
|
|
||||||
StringBuilder where = new StringBuilder("1=1");
|
|
||||||
if (stream > 0) {
|
|
||||||
where.append(" AND stream = ").append(stream);
|
|
||||||
}
|
|
||||||
if (version > 0) {
|
|
||||||
where.append(" AND version = ").append(version);
|
|
||||||
}
|
|
||||||
if (types.length > 0) {
|
|
||||||
where.append(" AND type IN (").append(join(types)).append(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
List<ObjectMessage> result = new LinkedList<>();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
where.toString(),
|
|
||||||
null, null, null, null
|
|
||||||
)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
int objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
|
||||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
|
||||||
result.add(Factory.getObjectMessage(objectVersion, new ByteArrayInputStream(blob),
|
|
||||||
blob.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void storeObject(ObjectMessage object) {
|
|
||||||
InventoryVector iv = object.getInventoryVector();
|
|
||||||
|
|
||||||
if (getCache(object.getStream()).containsKey(iv))
|
|
||||||
return;
|
|
||||||
|
|
||||||
LOG.trace("Storing object " + iv);
|
|
||||||
|
|
||||||
try {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
// Create a new map of values, where column names are the keys
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(COLUMN_HASH, object.getInventoryVector().getHash());
|
|
||||||
values.put(COLUMN_STREAM, object.getStream());
|
|
||||||
values.put(COLUMN_EXPIRES, object.getExpiresTime());
|
|
||||||
values.put(COLUMN_DATA, Encode.bytes(object));
|
|
||||||
values.put(COLUMN_TYPE, object.getType());
|
|
||||||
values.put(COLUMN_VERSION, object.getVersion());
|
|
||||||
|
|
||||||
db.insertOrThrow(TABLE_NAME, null, values);
|
|
||||||
|
|
||||||
getCache(object.getStream()).put(iv, object.getExpiresTime());
|
|
||||||
} catch (SQLiteConstraintException e) {
|
|
||||||
LOG.trace(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean contains(ObjectMessage object) {
|
|
||||||
return getCache(object.getStream()).keySet().contains(object.getInventoryVector());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanup() {
|
|
||||||
long fiveMinutesAgo = now() - 5 * MINUTE;
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
db.delete(TABLE_NAME, "expires < ?", new String[]{valueOf(fiveMinutesAgo)});
|
|
||||||
|
|
||||||
for (Map<InventoryVector, Long> c : cache.values()) {
|
|
||||||
Iterator<Map.Entry<InventoryVector, Long>> iterator = c.entrySet().iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
if (iterator.next().getValue() < fiveMinutesAgo) {
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* 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.repository
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
|
import ch.dissem.apps.abit.repository.SqlHelper.Companion.join
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectType
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.ports.Inventory
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Inventory] implementation using the Android SQL API.
|
||||||
|
*/
|
||||||
|
class AndroidInventory(private val sql: SqlHelper) : Inventory {
|
||||||
|
|
||||||
|
private val cache = ConcurrentHashMap<Long, MutableMap<InventoryVector, Long>>()
|
||||||
|
|
||||||
|
override fun getInventory(vararg streams: Long): List<InventoryVector> {
|
||||||
|
val result = LinkedList<InventoryVector>()
|
||||||
|
val now = now
|
||||||
|
for (stream in streams) {
|
||||||
|
for ((key, value) in getCache(stream)) {
|
||||||
|
if (value > now) {
|
||||||
|
result.add(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCache(stream: Long): MutableMap<InventoryVector, Long> {
|
||||||
|
fun addToCache(stream: Long): MutableMap<InventoryVector, Long> {
|
||||||
|
val result: MutableMap<InventoryVector, Long> = ConcurrentHashMap()
|
||||||
|
cache.put(stream, result)
|
||||||
|
|
||||||
|
val projection = arrayOf(COLUMN_HASH, COLUMN_EXPIRES)
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
db.query(
|
||||||
|
TABLE_NAME, projection,
|
||||||
|
"stream = $stream", null, null, null, null
|
||||||
|
).use { c ->
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
val blob = c.getBlob(c.getColumnIndex(COLUMN_HASH))
|
||||||
|
val expires = c.getLong(c.getColumnIndex(COLUMN_EXPIRES))
|
||||||
|
InventoryVector.fromHash(blob)?.let { result.put(it, expires) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("Stream #$stream inventory size: ${result.size}")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return cache[stream] ?: synchronized(cache) {
|
||||||
|
return@synchronized cache[stream] ?: addToCache(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getMissing(offer: List<InventoryVector>, vararg streams: Long) = offer - streams.flatMap { getCache(it).keys }
|
||||||
|
|
||||||
|
override fun getObject(vector: InventoryVector): ObjectMessage? {
|
||||||
|
// Define a projection that specifies which columns from the database
|
||||||
|
// you will actually use after this query.
|
||||||
|
val projection = arrayOf(COLUMN_VERSION, COLUMN_DATA)
|
||||||
|
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
db.query(
|
||||||
|
TABLE_NAME, projection,
|
||||||
|
"hash = X'$vector'", null, null, null, null
|
||||||
|
).use { c ->
|
||||||
|
if (!c.moveToFirst()) {
|
||||||
|
LOG.info("Object requested that we don't have. IV: {}", vector)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val version = c.getInt(c.getColumnIndex(COLUMN_VERSION))
|
||||||
|
val blob = c.getBlob(c.getColumnIndex(COLUMN_DATA))
|
||||||
|
return Factory.getObjectMessage(version, ByteArrayInputStream(blob), blob.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> {
|
||||||
|
// Define a projection that specifies which columns from the database
|
||||||
|
// you will actually use after this query.
|
||||||
|
val projection = arrayOf(COLUMN_VERSION, COLUMN_DATA)
|
||||||
|
val where = StringBuilder("1=1")
|
||||||
|
if (stream > 0) {
|
||||||
|
where.append(" AND stream = ").append(stream)
|
||||||
|
}
|
||||||
|
if (version > 0) {
|
||||||
|
where.append(" AND version = ").append(version)
|
||||||
|
}
|
||||||
|
if (types.isNotEmpty()) {
|
||||||
|
where.append(" AND type IN (").append(join(*types)).append(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
val result = LinkedList<ObjectMessage>()
|
||||||
|
db.query(
|
||||||
|
TABLE_NAME, projection,
|
||||||
|
where.toString(), null, null, null, null
|
||||||
|
).use { c ->
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
val objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION))
|
||||||
|
val blob = c.getBlob(c.getColumnIndex(COLUMN_DATA))
|
||||||
|
Factory.getObjectMessage(objectVersion, ByteArrayInputStream(blob), blob.size)?.let { result.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun storeObject(objectMessage: ObjectMessage) {
|
||||||
|
val iv = objectMessage.inventoryVector
|
||||||
|
|
||||||
|
if (getCache(objectMessage.stream).containsKey(iv))
|
||||||
|
return
|
||||||
|
|
||||||
|
LOG.trace("Storing object {}", iv)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
// Create a new map of values, where column names are the keys
|
||||||
|
val values = ContentValues()
|
||||||
|
values.put(COLUMN_HASH, objectMessage.inventoryVector.hash)
|
||||||
|
values.put(COLUMN_STREAM, objectMessage.stream)
|
||||||
|
values.put(COLUMN_EXPIRES, objectMessage.expiresTime)
|
||||||
|
values.put(COLUMN_DATA, Encode.bytes(objectMessage))
|
||||||
|
values.put(COLUMN_TYPE, objectMessage.type)
|
||||||
|
values.put(COLUMN_VERSION, objectMessage.version)
|
||||||
|
|
||||||
|
db.insertOrThrow(TABLE_NAME, null, values)
|
||||||
|
|
||||||
|
getCache(objectMessage.stream).put(iv, objectMessage.expiresTime)
|
||||||
|
} catch (e: SQLiteConstraintException) {
|
||||||
|
LOG.trace(e.message, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contains(objectMessage: ObjectMessage) = getCache(objectMessage.stream).keys.contains(objectMessage.inventoryVector)
|
||||||
|
|
||||||
|
override fun cleanup() {
|
||||||
|
val fiveMinutesAgo = now - 5 * MINUTE
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
db.delete(TABLE_NAME, "expires < ?", arrayOf(fiveMinutesAgo.toString()))
|
||||||
|
|
||||||
|
cache.values.map { it.entries }.forEach { entries -> entries.removeAll { it.value < fiveMinutesAgo } }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(AndroidInventory::class.java)
|
||||||
|
|
||||||
|
private const val TABLE_NAME = "Inventory"
|
||||||
|
private const val COLUMN_HASH = "hash"
|
||||||
|
private const val COLUMN_STREAM = "stream"
|
||||||
|
private const val COLUMN_EXPIRES = "expires"
|
||||||
|
private const val COLUMN_DATA = "data"
|
||||||
|
private const val COLUMN_TYPE = "type"
|
||||||
|
private const val COLUMN_VERSION = "version"
|
||||||
|
}
|
||||||
|
}
|
@ -30,10 +30,10 @@ import ch.dissem.bitmessage.entity.Plaintext
|
|||||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import ch.dissem.bitmessage.ports.AbstractMessageRepository
|
import ch.dissem.bitmessage.ports.AbstractMessageRepository
|
||||||
|
import ch.dissem.bitmessage.ports.AlreadyStoredException
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
import ch.dissem.bitmessage.utils.Encode
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
import ch.dissem.bitmessage.utils.Strings.hex
|
import ch.dissem.bitmessage.utils.Strings.hex
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -42,20 +42,16 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
class AndroidMessageRepository(private val sql: SqlHelper, private val context: Context) : AbstractMessageRepository() {
|
class AndroidMessageRepository(private val sql: SqlHelper, private val context: Context) : AbstractMessageRepository() {
|
||||||
|
|
||||||
override fun findMessages(label: Label?): List<Plaintext> {
|
override fun findMessages(label: Label?) = if (label === LABEL_ARCHIVE) {
|
||||||
if (label === LABEL_ARCHIVE) {
|
super.findMessages(null as Label?)
|
||||||
return super.findMessages(null as Label?)
|
} else {
|
||||||
} else {
|
super.findMessages(label)
|
||||||
return super.findMessages(label)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findMessageIds(label: Label): List<Long> {
|
fun findMessageIds(label: Label) = if (label === LABEL_ARCHIVE) {
|
||||||
if (label === LABEL_ARCHIVE) {
|
findIds("id NOT IN (SELECT message_id FROM Message_Label)")
|
||||||
return findIds("id NOT IN (SELECT message_id FROM Message_Label)")
|
} else {
|
||||||
} else {
|
findIds("id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id})")
|
||||||
return findIds("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun findLabels(where: String): List<Label> {
|
public override fun findLabels(where: String): List<Label> {
|
||||||
@ -65,8 +61,7 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
// you will actually use after this query.
|
// you will actually use after this query.
|
||||||
val projection = arrayOf(LBL_COLUMN_ID, LBL_COLUMN_LABEL, LBL_COLUMN_TYPE, LBL_COLUMN_COLOR)
|
val projection = arrayOf(LBL_COLUMN_ID, LBL_COLUMN_LABEL, LBL_COLUMN_TYPE, LBL_COLUMN_COLOR)
|
||||||
|
|
||||||
val db = sql.readableDatabase
|
sql.readableDatabase.query(
|
||||||
db.query(
|
|
||||||
LBL_TABLE_NAME, projection,
|
LBL_TABLE_NAME, projection,
|
||||||
where, null, null, null,
|
where, null, null, null,
|
||||||
LBL_COLUMN_ORDER
|
LBL_COLUMN_ORDER
|
||||||
@ -126,38 +121,33 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun countUnread(label: Label?): Int {
|
override fun countUnread(label: Label?) = when {
|
||||||
if (label === LABEL_ARCHIVE) {
|
label === LABEL_ARCHIVE -> 0
|
||||||
return 0
|
label == null -> DatabaseUtils.queryNumEntries(
|
||||||
} else if (label == null) {
|
sql.readableDatabase,
|
||||||
return DatabaseUtils.queryNumEntries(
|
TABLE_NAME,
|
||||||
sql.readableDatabase,
|
"id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
|
||||||
TABLE_NAME,
|
arrayOf(Label.Type.UNREAD.name)
|
||||||
"id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
|
).toInt()
|
||||||
arrayOf(Label.Type.UNREAD.name)
|
else -> DatabaseUtils.queryNumEntries(
|
||||||
).toInt()
|
sql.readableDatabase,
|
||||||
} else {
|
TABLE_NAME,
|
||||||
return DatabaseUtils.queryNumEntries(
|
" id IN (SELECT message_id FROM Message_Label WHERE label_id=?) " +
|
||||||
sql.readableDatabase,
|
"AND id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
|
||||||
TABLE_NAME,
|
arrayOf(label.id.toString(), Label.Type.UNREAD.name)
|
||||||
" id IN (SELECT message_id FROM Message_Label WHERE label_id=?) " + "AND id IN (SELECT message_id FROM Message_Label WHERE label_id IN (SELECT id FROM Label WHERE type=?))",
|
).toInt()
|
||||||
arrayOf(label.id.toString(), Label.Type.UNREAD.name)
|
|
||||||
).toInt()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findConversations(label: Label?): List<UUID> {
|
override fun findConversations(label: Label?): List<UUID> {
|
||||||
val projection = arrayOf(COLUMN_CONVERSATION)
|
val projection = arrayOf(COLUMN_CONVERSATION)
|
||||||
|
|
||||||
val where: String
|
val where = when {
|
||||||
if (label == null) {
|
label === LABEL_ARCHIVE -> "id NOT IN (SELECT message_id FROM Message_Label)"
|
||||||
where = "id NOT IN (SELECT message_id FROM Message_Label)"
|
label == null -> null
|
||||||
} else {
|
else -> "id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id})"
|
||||||
where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")"
|
|
||||||
}
|
}
|
||||||
val result = LinkedList<UUID>()
|
val result = LinkedList<UUID>()
|
||||||
val db = sql.readableDatabase
|
sql.readableDatabase.query(
|
||||||
db.query(
|
|
||||||
TABLE_NAME, projection,
|
TABLE_NAME, projection,
|
||||||
where, null, null, null, null
|
where, null, null, null, null
|
||||||
).use { c ->
|
).use { c ->
|
||||||
@ -321,7 +311,7 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
}
|
}
|
||||||
db.setTransactionSuccessful()
|
db.setTransactionSuccessful()
|
||||||
} catch (e: SQLiteConstraintException) {
|
} catch (e: SQLiteConstraintException) {
|
||||||
LOG.trace(e.message, e)
|
throw AlreadyStoredException(cause = e)
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction()
|
db.endTransaction()
|
||||||
}
|
}
|
||||||
@ -329,14 +319,10 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
|
|
||||||
private fun getValues(message: Plaintext): ContentValues {
|
private fun getValues(message: Plaintext): ContentValues {
|
||||||
val values = ContentValues()
|
val values = ContentValues()
|
||||||
values.put(COLUMN_IV, if (message.inventoryVector == null)
|
values.put(COLUMN_IV, message.inventoryVector?.hash)
|
||||||
null
|
|
||||||
else
|
|
||||||
message
|
|
||||||
.inventoryVector!!.hash)
|
|
||||||
values.put(COLUMN_TYPE, message.type.name)
|
values.put(COLUMN_TYPE, message.type.name)
|
||||||
values.put(COLUMN_SENDER, message.from.address)
|
values.put(COLUMN_SENDER, message.from.address)
|
||||||
values.put(COLUMN_RECIPIENT, if (message.to == null) null else message.to!!.address)
|
values.put(COLUMN_RECIPIENT, message.to?.address)
|
||||||
values.put(COLUMN_DATA, Encode.bytes(message))
|
values.put(COLUMN_DATA, Encode.bytes(message))
|
||||||
values.put(COLUMN_ACK_DATA, message.ackData)
|
values.put(COLUMN_ACK_DATA, message.ackData)
|
||||||
values.put(COLUMN_SENT, message.sent)
|
values.put(COLUMN_SENT, message.sent)
|
||||||
@ -365,9 +351,6 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG = LoggerFactory.getLogger(AndroidMessageRepository::class.java)
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val LABEL_ARCHIVE = Label("archive", null, 0)
|
val LABEL_ARCHIVE = Label("archive", null, 0)
|
||||||
|
|
||||||
private const val TABLE_NAME = "Message"
|
private const val TABLE_NAME = "Message"
|
||||||
|
@ -159,7 +159,6 @@ class AndroidNodeRegistry(private val sql: SqlHelper) : NodeRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
|
||||||
private val LOG = LoggerFactory.getLogger(AndroidInventory::class.java)
|
private val LOG = LoggerFactory.getLogger(AndroidInventory::class.java)
|
||||||
|
|
||||||
private const val TABLE_NAME = "Node"
|
private const val TABLE_NAME = "Node"
|
||||||
|
@ -1,175 +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.repository;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteConstraintException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.InternalContext;
|
|
||||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
|
||||||
import ch.dissem.bitmessage.factory.Factory;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
|
||||||
import ch.dissem.bitmessage.utils.Encode;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
import static ch.dissem.bitmessage.utils.Strings.hex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class AndroidProofOfWorkRepository implements ProofOfWorkRepository, InternalContext
|
|
||||||
.ContextHolder {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository.class);
|
|
||||||
|
|
||||||
private static final String TABLE_NAME = "POW";
|
|
||||||
private static final String COLUMN_INITIAL_HASH = "initial_hash";
|
|
||||||
private static final String COLUMN_DATA = "data";
|
|
||||||
private static final String COLUMN_VERSION = "version";
|
|
||||||
private static final String COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte";
|
|
||||||
private static final String COLUMN_EXTRA_BYTES = "extra_bytes";
|
|
||||||
private static final String COLUMN_EXPIRATION_TIME = "expiration_time";
|
|
||||||
private static final String COLUMN_MESSAGE_ID = "message_id";
|
|
||||||
|
|
||||||
private final SqlHelper sql;
|
|
||||||
private InternalContext bmc;
|
|
||||||
|
|
||||||
public AndroidProofOfWorkRepository(SqlHelper sql) {
|
|
||||||
this.sql = sql;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setContext(InternalContext internalContext) {
|
|
||||||
this.bmc = internalContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Item getItem(byte[] initialHash) {
|
|
||||||
// Define a projection that specifies which columns from the database
|
|
||||||
// you will actually use after this query.
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_DATA,
|
|
||||||
COLUMN_VERSION,
|
|
||||||
COLUMN_NONCE_TRIALS_PER_BYTE,
|
|
||||||
COLUMN_EXTRA_BYTES,
|
|
||||||
COLUMN_EXPIRATION_TIME,
|
|
||||||
COLUMN_MESSAGE_ID
|
|
||||||
};
|
|
||||||
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
"initial_hash=X'" + hex(initialHash) + "'",
|
|
||||||
null, null, null, null
|
|
||||||
)) {
|
|
||||||
if (c.moveToFirst()) {
|
|
||||||
int version = c.getInt(c.getColumnIndex(COLUMN_VERSION));
|
|
||||||
byte[] blob = c.getBlob(c.getColumnIndex(COLUMN_DATA));
|
|
||||||
if (c.isNull(c.getColumnIndex(COLUMN_MESSAGE_ID))) {
|
|
||||||
return new Item(
|
|
||||||
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
|
|
||||||
.length),
|
|
||||||
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
|
|
||||||
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return new Item(
|
|
||||||
Factory.getObjectMessage(version, new ByteArrayInputStream(blob), blob
|
|
||||||
.length),
|
|
||||||
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
|
|
||||||
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)),
|
|
||||||
c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)),
|
|
||||||
bmc.getMessageRepository().getMessage(
|
|
||||||
c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RuntimeException("Object requested that we don't have. Initial hash: " +
|
|
||||||
hex(initialHash));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<byte[]> getItems() {
|
|
||||||
// Define a projection that specifies which columns from the database
|
|
||||||
// you will actually use after this query.
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_INITIAL_HASH
|
|
||||||
};
|
|
||||||
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
List<byte[]> result = new LinkedList<>();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
null, null, null, null, null
|
|
||||||
)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
byte[] initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH));
|
|
||||||
result.add(initialHash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putObject(Item item) {
|
|
||||||
try {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
// Create a new map of values, where column names are the keys
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(COLUMN_INITIAL_HASH, cryptography().getInitialHash(item.getObjectMessage()));
|
|
||||||
values.put(COLUMN_DATA, Encode.bytes(item.getObjectMessage()));
|
|
||||||
values.put(COLUMN_VERSION, item.getObjectMessage().getVersion());
|
|
||||||
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, item.getNonceTrialsPerByte());
|
|
||||||
values.put(COLUMN_EXTRA_BYTES, item.getExtraBytes());
|
|
||||||
if (item.getMessage() != null) {
|
|
||||||
values.put(COLUMN_EXPIRATION_TIME, item.getExpirationTime());
|
|
||||||
values.put(COLUMN_MESSAGE_ID, (Long) item.getMessage().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
db.insertOrThrow(TABLE_NAME, null, values);
|
|
||||||
} catch (SQLiteConstraintException e) {
|
|
||||||
LOG.trace(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
|
|
||||||
putObject(new Item(object, nonceTrialsPerByte, extraBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeObject(byte[] initialHash) {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
db.delete(
|
|
||||||
TABLE_NAME,
|
|
||||||
"initial_hash=X'" + hex(initialHash) + "'",
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* 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.repository
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
|
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
||||||
|
import ch.dissem.bitmessage.utils.Encode
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import ch.dissem.bitmessage.utils.Strings.hex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class AndroidProofOfWorkRepository(private val sql: SqlHelper) : ProofOfWorkRepository, InternalContext.ContextHolder {
|
||||||
|
private lateinit var bmc: InternalContext
|
||||||
|
|
||||||
|
override fun setContext(context: InternalContext) {
|
||||||
|
this.bmc = context
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item {
|
||||||
|
// Define a projection that specifies which columns from the database
|
||||||
|
// you will actually use after this query.
|
||||||
|
val projection = arrayOf(COLUMN_DATA, COLUMN_VERSION, COLUMN_NONCE_TRIALS_PER_BYTE, COLUMN_EXTRA_BYTES, COLUMN_EXPIRATION_TIME, COLUMN_MESSAGE_ID)
|
||||||
|
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
db.query(
|
||||||
|
TABLE_NAME, projection,
|
||||||
|
"initial_hash=X'${hex(initialHash)}'",
|
||||||
|
null, null, null, null
|
||||||
|
).use { c ->
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
val version = c.getInt(c.getColumnIndex(COLUMN_VERSION))
|
||||||
|
val blob = c.getBlob(c.getColumnIndex(COLUMN_DATA))
|
||||||
|
return if (c.isNull(c.getColumnIndex(COLUMN_MESSAGE_ID))) {
|
||||||
|
ProofOfWorkRepository.Item(
|
||||||
|
Factory.getObjectMessage(version, ByteArrayInputStream(blob), blob.size) ?: throw RuntimeException("Invalid object in repository"),
|
||||||
|
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
|
||||||
|
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ProofOfWorkRepository.Item(
|
||||||
|
Factory.getObjectMessage(version, ByteArrayInputStream(blob), blob.size) ?: throw RuntimeException("Invalid object in repository"),
|
||||||
|
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
|
||||||
|
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)),
|
||||||
|
c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)),
|
||||||
|
bmc.messageRepository.getMessage(c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw RuntimeException("Object requested that we don't have. Initial hash: ${hex(initialHash)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItems(): List<ByteArray> {
|
||||||
|
// Define a projection that specifies which columns from the database
|
||||||
|
// you will actually use after this query.
|
||||||
|
val projection = arrayOf(COLUMN_INITIAL_HASH)
|
||||||
|
|
||||||
|
val db = sql.readableDatabase
|
||||||
|
val result = LinkedList<ByteArray>()
|
||||||
|
db.query(
|
||||||
|
TABLE_NAME, projection, null, null, null, null, null
|
||||||
|
).use { c ->
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
val initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH))
|
||||||
|
result.add(initialHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putObject(item: ProofOfWorkRepository.Item) {
|
||||||
|
try {
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
// Create a new map of values, where column names are the keys
|
||||||
|
val values = ContentValues()
|
||||||
|
values.put(COLUMN_INITIAL_HASH, cryptography().getInitialHash(item.objectMessage))
|
||||||
|
values.put(COLUMN_DATA, Encode.bytes(item.objectMessage))
|
||||||
|
values.put(COLUMN_VERSION, item.objectMessage.version)
|
||||||
|
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, item.nonceTrialsPerByte)
|
||||||
|
values.put(COLUMN_EXTRA_BYTES, item.extraBytes)
|
||||||
|
item.message?.let { message ->
|
||||||
|
values.put(COLUMN_EXPIRATION_TIME, item.expirationTime)
|
||||||
|
values.put(COLUMN_MESSAGE_ID, message.id as Long?)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.insertOrThrow(TABLE_NAME, null, values)
|
||||||
|
} catch (e: SQLiteConstraintException) {
|
||||||
|
LOG.trace(e.message, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
||||||
|
putObject(ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeObject(initialHash: ByteArray) {
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
db.delete(TABLE_NAME, "initial_hash=X'${hex(initialHash)}'", null)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOG = LoggerFactory.getLogger(AndroidProofOfWorkRepository::class.java)
|
||||||
|
|
||||||
|
private val TABLE_NAME = "POW"
|
||||||
|
private val COLUMN_INITIAL_HASH = "initial_hash"
|
||||||
|
private val COLUMN_DATA = "data"
|
||||||
|
private val COLUMN_VERSION = "version"
|
||||||
|
private val COLUMN_NONCE_TRIALS_PER_BYTE = "nonce_trials_per_byte"
|
||||||
|
private val COLUMN_EXTRA_BYTES = "extra_bytes"
|
||||||
|
private val COLUMN_EXPIRATION_TIME = "expiration_time"
|
||||||
|
private val COLUMN_MESSAGE_ID = "message_id"
|
||||||
|
}
|
||||||
|
}
|
@ -1,125 +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.repository;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.util.Assets;
|
|
||||||
import ch.dissem.apps.abit.util.UuidUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles database migration and provides access.
|
|
||||||
*/
|
|
||||||
public class SqlHelper extends SQLiteOpenHelper {
|
|
||||||
// If you change the database schema, you must increment the database version.
|
|
||||||
private static final int DATABASE_VERSION = 7;
|
|
||||||
private static final String DATABASE_NAME = "jabit.db";
|
|
||||||
|
|
||||||
private final Context ctx;
|
|
||||||
|
|
||||||
public SqlHelper(Context ctx) {
|
|
||||||
super(ctx, DATABASE_NAME, null, DATABASE_VERSION);
|
|
||||||
this.ctx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
onUpgrade(db, 0, DATABASE_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
switch (oldVersion) {
|
|
||||||
case 0:
|
|
||||||
executeMigration(db, "V1.0__Create_table_inventory");
|
|
||||||
executeMigration(db, "V1.1__Create_table_address");
|
|
||||||
executeMigration(db, "V1.2__Create_table_message");
|
|
||||||
case 1:
|
|
||||||
// executeMigration(db, "V2.0__Update_table_message");
|
|
||||||
executeMigration(db, "V2.1__Create_table_POW");
|
|
||||||
case 2:
|
|
||||||
executeMigration(db, "V3.0__Update_table_address");
|
|
||||||
case 3:
|
|
||||||
executeMigration(db, "V3.1__Update_table_POW");
|
|
||||||
executeMigration(db, "V3.2__Update_table_message");
|
|
||||||
case 4:
|
|
||||||
executeMigration(db, "V3.3__Create_table_node");
|
|
||||||
case 5:
|
|
||||||
executeMigration(db, "V3.4__Add_label_outbox");
|
|
||||||
case 6:
|
|
||||||
executeMigration(db, "V4.0__Create_table_message_parent");
|
|
||||||
case 7:
|
|
||||||
setMissingConversationIds(db);
|
|
||||||
default:
|
|
||||||
// Nothing to do. Let's assume we won't upgrade from a version that's newer than
|
|
||||||
// DATABASE_VERSION.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set UUIDs for all messages that have no conversation ID
|
|
||||||
*/
|
|
||||||
private void setMissingConversationIds(SQLiteDatabase db) {
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
"Message", new String[]{"id"},
|
|
||||||
"conversation IS NULL",
|
|
||||||
null, null, null, null
|
|
||||||
)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
long id = c.getLong(0);
|
|
||||||
setMissingConversationId(id, db);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setMissingConversationId(long id, SQLiteDatabase db) {
|
|
||||||
ContentValues values = new ContentValues(1);
|
|
||||||
values.put("conversation", UuidUtils.asBytes(UUID.randomUUID()));
|
|
||||||
db.update("Message", values, "id=?", new String[]{String.valueOf(id)});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeMigration(SQLiteDatabase db, String name) {
|
|
||||||
for (String statement : Assets.readSqlStatements(ctx, "db/migration/" + name + ".sql")) {
|
|
||||||
db.execSQL(statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static StringBuilder join(long... numbers) {
|
|
||||||
StringBuilder streamList = new StringBuilder();
|
|
||||||
for (int i = 0; i < numbers.length; i++) {
|
|
||||||
if (i > 0) streamList.append(", ");
|
|
||||||
streamList.append(numbers[i]);
|
|
||||||
}
|
|
||||||
return streamList;
|
|
||||||
}
|
|
||||||
|
|
||||||
static StringBuilder join(Enum<?>... types) {
|
|
||||||
StringBuilder streamList = new StringBuilder();
|
|
||||||
for (int i = 0; i < types.length; i++) {
|
|
||||||
if (i > 0) streamList.append(", ");
|
|
||||||
streamList.append('\'').append(types[i].name()).append('\'');
|
|
||||||
}
|
|
||||||
return streamList;
|
|
||||||
}
|
|
||||||
}
|
|
104
app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.kt
Normal file
104
app/src/main/java/ch/dissem/apps/abit/repository/SqlHelper.kt
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.repository
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
import ch.dissem.apps.abit.util.Assets
|
||||||
|
import ch.dissem.apps.abit.util.UuidUtils
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles database migration and provides access.
|
||||||
|
*/
|
||||||
|
class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||||
|
|
||||||
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
|
onUpgrade(db, 0, DATABASE_VERSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
mapOf(
|
||||||
|
0 to {
|
||||||
|
executeMigration(db, "V1.0__Create_table_inventory")
|
||||||
|
executeMigration(db, "V1.1__Create_table_address")
|
||||||
|
executeMigration(db, "V1.2__Create_table_message")
|
||||||
|
},
|
||||||
|
1 to {
|
||||||
|
// executeMigration(db, "V2.0__Update_table_message");
|
||||||
|
executeMigration(db, "V2.1__Create_table_POW")
|
||||||
|
},
|
||||||
|
2 to {
|
||||||
|
executeMigration(db, "V3.0__Update_table_address")
|
||||||
|
},
|
||||||
|
3 to {
|
||||||
|
executeMigration(db, "V3.1__Update_table_POW")
|
||||||
|
executeMigration(db, "V3.2__Update_table_message")
|
||||||
|
},
|
||||||
|
4 to {
|
||||||
|
executeMigration(db, "V3.3__Create_table_node")
|
||||||
|
},
|
||||||
|
5 to {
|
||||||
|
executeMigration(db, "V3.4__Add_label_outbox")
|
||||||
|
},
|
||||||
|
6 to {
|
||||||
|
executeMigration(db, "V4.0__Create_table_message_parent")
|
||||||
|
},
|
||||||
|
7 to {
|
||||||
|
setMissingConversationIds(db)
|
||||||
|
}
|
||||||
|
).filterKeys { it in oldVersion..(newVersion - 1) }.forEach { (_, v) -> v.invoke() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set UUIDs for all messages that have no conversation ID
|
||||||
|
*/
|
||||||
|
private fun setMissingConversationIds(db: SQLiteDatabase) {
|
||||||
|
db.query(
|
||||||
|
"Message", arrayOf("id"),
|
||||||
|
"conversation IS NULL", null, null, null, null
|
||||||
|
).use { c ->
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
val id = c.getLong(0)
|
||||||
|
setMissingConversationId(id, db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMissingConversationId(id: Long, db: SQLiteDatabase) {
|
||||||
|
val values = ContentValues(1)
|
||||||
|
values.put("conversation", UuidUtils.asBytes(UUID.randomUUID()))
|
||||||
|
db.update("Message", values, "id=?", arrayOf(id.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun executeMigration(db: SQLiteDatabase, name: String) {
|
||||||
|
for (statement in Assets.readSqlStatements(ctx, "db/migration/$name.sql")) {
|
||||||
|
db.execSQL(statement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// If you change the database schema, you must increment the database version.
|
||||||
|
private val DATABASE_VERSION = 7
|
||||||
|
private val DATABASE_NAME = "jabit.db"
|
||||||
|
|
||||||
|
internal fun join(vararg types: Enum<*>): String = types.joinToString(separator = "', '", prefix = "'", postfix = "'", transform = {it.name})
|
||||||
|
}
|
||||||
|
}
|
@ -18,15 +18,10 @@ package ch.dissem.apps.abit.service
|
|||||||
|
|
||||||
import android.app.IntentService
|
import android.app.IntentService
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
||||||
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
|
|
||||||
import ch.dissem.apps.abit.util.NetworkUtils
|
import ch.dissem.apps.abit.util.NetworkUtils
|
||||||
import ch.dissem.apps.abit.util.Preferences
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
|
||||||
import ch.dissem.apps.abit.MainActivity.updateNodeSwitch
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
|
@ -35,7 +35,7 @@ class BitmessageService : Service() {
|
|||||||
private lateinit var notification: NetworkNotification
|
private lateinit var notification: NetworkNotification
|
||||||
|
|
||||||
private val cleanupHandler = Handler()
|
private val cleanupHandler = Handler()
|
||||||
private val cleanupTask = object : Runnable {
|
private val cleanupTask: Runnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
bmc.cleanup()
|
bmc.cleanup()
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
@ -79,11 +79,9 @@ class BitmessageService : Service() {
|
|||||||
companion object {
|
companion object {
|
||||||
@Volatile private var running = false
|
@Volatile private var running = false
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
val isRunning: Boolean
|
val isRunning: Boolean
|
||||||
get() = running && Singleton.bitmessageContext?.isRunning() ?: false
|
get() = running && Singleton.bitmessageContext?.isRunning() ?: false
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
val status: Property
|
val status: Property
|
||||||
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")
|
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")
|
||||||
}
|
}
|
||||||
|
@ -1,78 +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.service;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.os.IBinder;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.service.ProofOfWorkService.PowBinder;
|
|
||||||
import ch.dissem.apps.abit.service.ProofOfWorkService.PowItem;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
|
||||||
|
|
||||||
import static android.content.Context.BIND_AUTO_CREATE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proof of Work engine that uses the Proof of Work service.
|
|
||||||
*/
|
|
||||||
public class ServicePowEngine implements ProofOfWorkEngine {
|
|
||||||
private final Context ctx;
|
|
||||||
|
|
||||||
private static final Object lock = new Object();
|
|
||||||
private final Queue<PowItem> queue = new LinkedList<>();
|
|
||||||
private PowBinder service;
|
|
||||||
|
|
||||||
public ServicePowEngine(Context ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ServiceConnection connection = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
||||||
synchronized (lock) {
|
|
||||||
ServicePowEngine.this.service = (PowBinder) service;
|
|
||||||
while (!queue.isEmpty()) {
|
|
||||||
ServicePowEngine.this.service.process(queue.poll());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
|
||||||
service = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void calculateNonce(byte[] initialHash, byte[] targetValue, Callback callback) {
|
|
||||||
PowItem item = new PowItem(initialHash, targetValue, callback);
|
|
||||||
synchronized (lock) {
|
|
||||||
if (service != null) {
|
|
||||||
service.process(item);
|
|
||||||
} else {
|
|
||||||
queue.add(item);
|
|
||||||
ctx.bindService(new Intent(ctx, ProofOfWorkService.class), connection,
|
|
||||||
BIND_AUTO_CREATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.service
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.os.IBinder
|
||||||
|
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
import ch.dissem.apps.abit.service.ProofOfWorkService.PowBinder
|
||||||
|
import ch.dissem.apps.abit.service.ProofOfWorkService.PowItem
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
|
||||||
|
|
||||||
|
import android.content.Context.BIND_AUTO_CREATE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proof of Work engine that uses the Proof of Work service.
|
||||||
|
*/
|
||||||
|
class ServicePowEngine(private val ctx: Context) : ProofOfWorkEngine {
|
||||||
|
private val queue = LinkedList<PowItem>()
|
||||||
|
private var service: PowBinder? = null
|
||||||
|
|
||||||
|
private val connection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
|
synchronized(lock) {
|
||||||
|
this@ServicePowEngine.service = service as PowBinder
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
this@ServicePowEngine.service!!.process(queue.poll())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
service = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
|
||||||
|
val item = PowItem(initialHash, target, callback)
|
||||||
|
synchronized(lock) {
|
||||||
|
service?.process(item) ?: {
|
||||||
|
queue.add(item)
|
||||||
|
ctx.bindService(Intent(ctx, ProofOfWorkService::class.java), connection,
|
||||||
|
BIND_AUTO_CREATE)
|
||||||
|
}.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val lock = Any()
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,6 @@
|
|||||||
package ch.dissem.apps.abit.service
|
package ch.dissem.apps.abit.service
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.AsyncTask
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
@ -35,6 +34,8 @@ import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
|||||||
import ch.dissem.bitmessage.utils.ConversationService
|
import ch.dissem.bitmessage.utils.ConversationService
|
||||||
import ch.dissem.bitmessage.utils.TTL
|
import ch.dissem.bitmessage.utils.TTL
|
||||||
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
import org.jetbrains.anko.doAsync
|
||||||
|
import org.jetbrains.anko.uiThread
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides singleton objects across the application.
|
* Provides singleton objects across the application.
|
||||||
@ -48,121 +49,101 @@ object Singleton {
|
|||||||
private var powRepo: AndroidProofOfWorkRepository? = null
|
private var powRepo: AndroidProofOfWorkRepository? = null
|
||||||
private var creatingIdentity: Boolean = false
|
private var creatingIdentity: Boolean = false
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getBitmessageContext(context: Context): BitmessageContext {
|
fun getBitmessageContext(context: Context): BitmessageContext {
|
||||||
if (bitmessageContext == null) {
|
return init({ bitmessageContext }, { bitmessageContext = it }) {
|
||||||
synchronized(Singleton::class.java) {
|
val ctx = context.applicationContext
|
||||||
if (bitmessageContext == null) {
|
val sqlHelper = SqlHelper(ctx)
|
||||||
val ctx = context.applicationContext
|
powRepo = AndroidProofOfWorkRepository(sqlHelper)
|
||||||
val sqlHelper = SqlHelper(ctx)
|
TTL.pubkey = 2 * DAY
|
||||||
powRepo = AndroidProofOfWorkRepository(sqlHelper)
|
BitmessageContext.Builder()
|
||||||
TTL.pubkey = 2 * DAY
|
.proofOfWorkEngine(SwitchingProofOfWorkEngine(
|
||||||
bitmessageContext = BitmessageContext.Builder()
|
|
||||||
.proofOfWorkEngine(SwitchingProofOfWorkEngine(
|
|
||||||
ctx, Constants.PREFERENCE_SERVER_POW,
|
ctx, Constants.PREFERENCE_SERVER_POW,
|
||||||
ServerPowEngine(ctx),
|
ServerPowEngine(ctx),
|
||||||
ServicePowEngine(ctx)
|
ServicePowEngine(ctx)
|
||||||
))
|
))
|
||||||
.cryptography(AndroidCryptography())
|
.cryptography(AndroidCryptography())
|
||||||
.nodeRegistry(AndroidNodeRegistry(sqlHelper))
|
.nodeRegistry(AndroidNodeRegistry(sqlHelper))
|
||||||
.inventory(AndroidInventory(sqlHelper))
|
.inventory(AndroidInventory(sqlHelper))
|
||||||
.addressRepo(AndroidAddressRepository(sqlHelper))
|
.addressRepo(AndroidAddressRepository(sqlHelper))
|
||||||
.messageRepo(AndroidMessageRepository(sqlHelper, ctx))
|
.messageRepo(AndroidMessageRepository(sqlHelper, ctx))
|
||||||
.powRepo(powRepo!!)
|
.powRepo(powRepo!!)
|
||||||
.networkHandler(NioNetworkHandler())
|
.networkHandler(NioNetworkHandler())
|
||||||
.listener(getMessageListener(ctx))
|
.listener(getMessageListener(ctx))
|
||||||
.doNotSendPubkeyOnIdentityCreation()
|
.doNotSendPubkeyOnIdentityCreation()
|
||||||
.build()
|
.build()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return bitmessageContext!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
fun getMessageListener(ctx: Context) = init({ messageListener }, { messageListener = it }) { MessageListener(ctx) }
|
||||||
fun getMessageListener(ctx: Context): MessageListener {
|
|
||||||
if (messageListener == null) {
|
|
||||||
synchronized(Singleton::class.java) {
|
|
||||||
if (messageListener == null) {
|
|
||||||
messageListener = MessageListener(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return messageListener!!
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
fun getMessageRepository(ctx: Context) = getBitmessageContext(ctx).messages as AndroidMessageRepository
|
||||||
fun getMessageRepository(ctx: Context): AndroidMessageRepository {
|
|
||||||
return getBitmessageContext(ctx).messages as AndroidMessageRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
fun getAddressRepository(ctx: Context) = getBitmessageContext(ctx).addresses as AndroidAddressRepository
|
||||||
fun getAddressRepository(ctx: Context): AndroidAddressRepository {
|
|
||||||
return getBitmessageContext(ctx).addresses as AndroidAddressRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getProofOfWorkRepository(ctx: Context): ProofOfWorkRepository {
|
fun getProofOfWorkRepository(ctx: Context): ProofOfWorkRepository {
|
||||||
if (powRepo == null) getBitmessageContext(ctx)
|
if (powRepo == null) getBitmessageContext(ctx)
|
||||||
return powRepo!!
|
return powRepo!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getIdentity(ctx: Context): BitmessageAddress? {
|
fun getIdentity(ctx: Context): BitmessageAddress? {
|
||||||
if (identity == null) {
|
return init<BitmessageAddress?>(ctx, { identity }, { identity = it }) { bmc ->
|
||||||
val bmc = getBitmessageContext(ctx)
|
val identities = bmc.addresses.getIdentities()
|
||||||
synchronized(Singleton::class) {
|
if (identities.isNotEmpty()) {
|
||||||
if (identity == null) {
|
identities[0]
|
||||||
val identities = bmc.addresses.getIdentities()
|
} else {
|
||||||
if (identities.isNotEmpty()) {
|
if (!creatingIdentity) {
|
||||||
identity = identities[0]
|
creatingIdentity = true
|
||||||
} else {
|
doAsync {
|
||||||
if (!creatingIdentity) {
|
val identity = bmc.createIdentity(false,
|
||||||
creatingIdentity = true
|
Pubkey.Feature.DOES_ACK)
|
||||||
object : AsyncTask<Void, Void, BitmessageAddress>() {
|
identity.alias = ctx.getString(R.string.alias_default_identity)
|
||||||
override fun doInBackground(vararg args: Void): BitmessageAddress {
|
bmc.addresses.save(identity)
|
||||||
val identity = bmc.createIdentity(false,
|
|
||||||
Pubkey.Feature.DOES_ACK)
|
|
||||||
identity.alias = ctx.getString(R.string.alias_default_identity)
|
|
||||||
bmc.addresses.save(identity)
|
|
||||||
return identity
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostExecute(identity: BitmessageAddress) {
|
uiThread {
|
||||||
Singleton.identity = identity
|
Singleton.identity = identity
|
||||||
Toast.makeText(ctx,
|
Toast.makeText(ctx,
|
||||||
R.string.toast_identity_created,
|
R.string.toast_identity_created,
|
||||||
Toast.LENGTH_SHORT).show()
|
Toast.LENGTH_SHORT).show()
|
||||||
val mainActivity = MainActivity.getInstance()
|
val mainActivity = MainActivity.getInstance()
|
||||||
mainActivity?.addIdentityEntry(identity)
|
mainActivity?.addIdentityEntry(identity)
|
||||||
}
|
|
||||||
}.execute()
|
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return identity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setIdentity(identity: BitmessageAddress) {
|
fun setIdentity(identity: BitmessageAddress) {
|
||||||
if (identity.privateKey == null)
|
if (identity.privateKey == null)
|
||||||
throw IllegalArgumentException("Identity expected, but no private key available")
|
throw IllegalArgumentException("Identity expected, but no private key available")
|
||||||
Singleton.identity = identity
|
Singleton.identity = identity
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
fun getConversationService(ctx: Context) = init(ctx, { conversationService }, { conversationService = it }) { ConversationService(it.messages) }
|
||||||
fun getConversationService(ctx: Context): ConversationService {
|
|
||||||
if (conversationService == null) {
|
private inline fun <T> init(crossinline getter: () -> T?, crossinline setter: (T) -> Unit, crossinline creator: () -> T): T {
|
||||||
val bmc = getBitmessageContext(ctx)
|
return getter() ?: {
|
||||||
synchronized(Singleton::class.java) {
|
synchronized(Singleton) {
|
||||||
if (conversationService == null) {
|
getter() ?: {
|
||||||
conversationService = ConversationService(bmc.messages)
|
val v = creator()
|
||||||
}
|
setter(v)
|
||||||
|
v
|
||||||
|
}.invoke()
|
||||||
}
|
}
|
||||||
}
|
}.invoke()
|
||||||
return conversationService!!
|
}
|
||||||
|
|
||||||
|
private inline fun <T> init(ctx: Context, crossinline getter: () -> T?, crossinline setter: (T) -> Unit, crossinline creator: (BitmessageContext) -> T): T {
|
||||||
|
return getter() ?: {
|
||||||
|
val bmc = getBitmessageContext(ctx)
|
||||||
|
synchronized(Singleton) {
|
||||||
|
getter() ?: {
|
||||||
|
val v = creator(bmc)
|
||||||
|
setter(v)
|
||||||
|
v
|
||||||
|
}.invoke()
|
||||||
|
}
|
||||||
|
}.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,11 @@ import android.support.annotation.RequiresApi
|
|||||||
import ch.dissem.apps.abit.util.Preferences
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by chrigu on 18.08.17.
|
* Starts the full node if
|
||||||
|
* * it is active
|
||||||
|
* * it is not already running
|
||||||
|
*
|
||||||
|
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
class StartupNodeOnWifiService : JobService() {
|
class StartupNodeOnWifiService : JobService() {
|
||||||
|
@ -29,51 +29,34 @@ import android.os.Bundle
|
|||||||
*/
|
*/
|
||||||
class Authenticator(context: Context) : AbstractAccountAuthenticator(context) {
|
class Authenticator(context: Context) : AbstractAccountAuthenticator(context) {
|
||||||
|
|
||||||
// Editing properties is not supported
|
override fun editProperties(r: AccountAuthenticatorResponse, s: String) =
|
||||||
override fun editProperties(r: AccountAuthenticatorResponse, s: String) = throw UnsupportedOperationException()
|
throw UnsupportedOperationException("Editing properties is not supported")
|
||||||
|
|
||||||
// Don't add additional accounts
|
// Don't add additional accounts
|
||||||
@Throws(NetworkErrorException::class)
|
@Throws(NetworkErrorException::class)
|
||||||
override fun addAccount(
|
override fun addAccount(r: AccountAuthenticatorResponse, s: String, s2: String, strings: Array<String>, bundle: Bundle) = null
|
||||||
r: AccountAuthenticatorResponse,
|
|
||||||
s: String,
|
|
||||||
s2: String,
|
|
||||||
strings: Array<String>,
|
|
||||||
bundle: Bundle) = null
|
|
||||||
|
|
||||||
// Ignore attempts to confirm credentials
|
// Ignore attempts to confirm credentials
|
||||||
@Throws(NetworkErrorException::class)
|
@Throws(NetworkErrorException::class)
|
||||||
override fun confirmCredentials(
|
override fun confirmCredentials(r: AccountAuthenticatorResponse, account: Account, bundle: Bundle) = null
|
||||||
r: AccountAuthenticatorResponse,
|
|
||||||
account: Account,
|
|
||||||
bundle: Bundle) = null
|
|
||||||
|
|
||||||
// Getting an authentication token is not supported
|
|
||||||
@Throws(NetworkErrorException::class)
|
@Throws(NetworkErrorException::class)
|
||||||
override fun getAuthToken(
|
override fun getAuthToken(r: AccountAuthenticatorResponse, account: Account, s: String, bundle: Bundle) =
|
||||||
r: AccountAuthenticatorResponse,
|
throw UnsupportedOperationException("Getting an authentication token is not supported")
|
||||||
account: Account,
|
|
||||||
s: String,
|
|
||||||
bundle: Bundle) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// Getting a label for the auth token is not supported
|
override fun getAuthTokenLabel(s: String) =
|
||||||
override fun getAuthTokenLabel(s: String) = throw UnsupportedOperationException()
|
throw UnsupportedOperationException("Getting a label for the auth token is not supported")
|
||||||
|
|
||||||
// Updating user credentials is not supported
|
|
||||||
@Throws(NetworkErrorException::class)
|
@Throws(NetworkErrorException::class)
|
||||||
override fun updateCredentials(
|
override fun updateCredentials(r: AccountAuthenticatorResponse, account: Account, s: String, bundle: Bundle) =
|
||||||
r: AccountAuthenticatorResponse,
|
throw UnsupportedOperationException("Updating user credentials is not supported")
|
||||||
account: Account,
|
|
||||||
s: String, bundle: Bundle) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// Checking features for the account is not supported
|
|
||||||
@Throws(NetworkErrorException::class)
|
@Throws(NetworkErrorException::class)
|
||||||
override fun hasFeatures(
|
override fun hasFeatures(r: AccountAuthenticatorResponse, account: Account, strings: Array<String>) =
|
||||||
r: AccountAuthenticatorResponse,
|
throw UnsupportedOperationException("Checking features for the account is not supported")
|
||||||
account: Account, strings: Array<String>) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField val ACCOUNT_SYNC = Account("Bitmessage", "ch.dissem.bitmessage")
|
val ACCOUNT_SYNC = Account("Bitmessage", "ch.dissem.bitmessage")
|
||||||
@JvmField val ACCOUNT_POW = Account("Proof of Work ", "ch.dissem.bitmessage")
|
val ACCOUNT_POW = Account("Proof of Work ", "ch.dissem.bitmessage")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,11 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
private val bmc = Singleton.getBitmessageContext(context)
|
private val bmc = Singleton.getBitmessageContext(context)
|
||||||
|
|
||||||
override fun onPerformSync(
|
override fun onPerformSync(
|
||||||
account: Account,
|
account: Account,
|
||||||
extras: Bundle,
|
extras: Bundle,
|
||||||
authority: String,
|
authority: String,
|
||||||
provider: ContentProviderClient,
|
provider: ContentProviderClient,
|
||||||
syncResult: SyncResult
|
syncResult: SyncResult
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
if (account == ACCOUNT_SYNC) {
|
if (account == ACCOUNT_SYNC) {
|
||||||
@ -81,10 +81,10 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
}
|
}
|
||||||
LOG.info("Synchronization started")
|
LOG.info("Synchronization started")
|
||||||
bmc.synchronize(
|
bmc.synchronize(
|
||||||
trustedNode,
|
trustedNode,
|
||||||
Preferences.getTrustedNodePort(context),
|
Preferences.getTrustedNodePort(context),
|
||||||
Preferences.getTimeoutInSeconds(context),
|
Preferences.getTimeoutInSeconds(context),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
LOG.info("Synchronization finished")
|
LOG.info("Synchronization finished")
|
||||||
}
|
}
|
||||||
@ -113,12 +113,12 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
|
||||||
val target = cryptography().getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
val target = cryptography().getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
||||||
val cryptoMsg = CryptoCustomMessage(
|
val cryptoMsg = CryptoCustomMessage(
|
||||||
ProofOfWorkRequest(identity, initialHash, CALCULATE, target))
|
ProofOfWorkRequest(identity, initialHash, CALCULATE, target))
|
||||||
cryptoMsg.signAndEncrypt(identity, signingKey)
|
cryptoMsg.signAndEncrypt(identity, signingKey)
|
||||||
val response = bmc.send(
|
val response = bmc.send(
|
||||||
trustedNode,
|
trustedNode,
|
||||||
Preferences.getTrustedNodePort(context),
|
Preferences.getTrustedNodePort(context),
|
||||||
cryptoMsg
|
cryptoMsg
|
||||||
)
|
)
|
||||||
if (response.isError) {
|
if (response.isError) {
|
||||||
LOG.error("Server responded with error: ${String(response.getData())}")
|
LOG.error("Server responded with error: ${String(response.getData())}")
|
||||||
@ -140,7 +140,6 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
|
|
||||||
private const val SYNC_FREQUENCY = 15 * 60L // seconds
|
private const val SYNC_FREQUENCY = 15 * 60L // seconds
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun startSync(ctx: Context) {
|
fun startSync(ctx: Context) {
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||||
val account = addAccount(ctx, ACCOUNT_SYNC)
|
val account = addAccount(ctx, ACCOUNT_SYNC)
|
||||||
@ -150,7 +149,6 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
|
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun stopSync(ctx: Context) {
|
fun stopSync(ctx: Context) {
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||||
val account = addAccount(ctx, ACCOUNT_SYNC)
|
val account = addAccount(ctx, ACCOUNT_SYNC)
|
||||||
@ -158,7 +156,6 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle())
|
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle())
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun startPowSync(ctx: Context) {
|
fun startPowSync(ctx: Context) {
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||||
val account = addAccount(ctx, ACCOUNT_POW)
|
val account = addAccount(ctx, ACCOUNT_POW)
|
||||||
@ -168,7 +165,6 @@ class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedS
|
|||||||
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
|
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun stopPowSync(ctx: Context) {
|
fun stopPowSync(ctx: Context) {
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
||||||
val account = addAccount(ctx, ACCOUNT_POW)
|
val account = addAccount(ctx, ACCOUNT_POW)
|
||||||
|
@ -1,92 +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.util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.annotation.DrawableRes;
|
|
||||||
import android.support.annotation.StringRes;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.bitmessage.entity.Plaintext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to work with Assets.
|
|
||||||
*/
|
|
||||||
public class Assets {
|
|
||||||
public static List<String> readSqlStatements(Context ctx, String name) {
|
|
||||||
try {
|
|
||||||
InputStream in = ctx.getAssets().open(name);
|
|
||||||
Scanner scanner = new Scanner(in, "UTF-8").useDelimiter(";");
|
|
||||||
List<String> result = new LinkedList<>();
|
|
||||||
while (scanner.hasNext()) {
|
|
||||||
String statement = scanner.next().trim();
|
|
||||||
if (!"".equals(statement)) {
|
|
||||||
result.add(statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@DrawableRes
|
|
||||||
public static int getStatusDrawable(Plaintext.Status status) {
|
|
||||||
switch (status) {
|
|
||||||
case RECEIVED:
|
|
||||||
return 0;
|
|
||||||
case DRAFT:
|
|
||||||
return R.drawable.draft;
|
|
||||||
case PUBKEY_REQUESTED:
|
|
||||||
return R.drawable.public_key;
|
|
||||||
case DOING_PROOF_OF_WORK:
|
|
||||||
return R.drawable.ic_notification_proof_of_work;
|
|
||||||
case SENT:
|
|
||||||
return R.drawable.sent;
|
|
||||||
case SENT_ACKNOWLEDGED:
|
|
||||||
return R.drawable.sent_acknowledged;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
public static int getStatusString(Plaintext.Status status) {
|
|
||||||
switch (status) {
|
|
||||||
case RECEIVED:
|
|
||||||
return R.string.status_received;
|
|
||||||
case DRAFT:
|
|
||||||
return R.string.status_draft;
|
|
||||||
case PUBKEY_REQUESTED:
|
|
||||||
return R.string.status_public_key;
|
|
||||||
case DOING_PROOF_OF_WORK:
|
|
||||||
return R.string.proof_of_work_title;
|
|
||||||
case SENT:
|
|
||||||
return R.string.status_sent;
|
|
||||||
case SENT_ACKNOWLEDGED:
|
|
||||||
return R.string.status_sent_acknowledged;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
74
app/src/main/java/ch/dissem/apps/abit/util/Assets.kt
Normal file
74
app/src/main/java/ch/dissem/apps/abit/util/Assets.kt
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.annotation.DrawableRes
|
||||||
|
import android.support.annotation.StringRes
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to work with Assets.
|
||||||
|
*/
|
||||||
|
object Assets {
|
||||||
|
fun readSqlStatements(ctx: Context, name: String): List<String> {
|
||||||
|
try {
|
||||||
|
val `in` = ctx.assets.open(name)
|
||||||
|
val scanner = Scanner(`in`, "UTF-8").useDelimiter(";")
|
||||||
|
val result = LinkedList<String>()
|
||||||
|
while (scanner.hasNext()) {
|
||||||
|
val statement = scanner.next().trim { it <= ' ' }
|
||||||
|
if ("" != statement) {
|
||||||
|
result.add(statement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
fun getStatusDrawable(status: Plaintext.Status): Int {
|
||||||
|
when (status) {
|
||||||
|
Plaintext.Status.RECEIVED -> return 0
|
||||||
|
Plaintext.Status.DRAFT -> return R.drawable.draft
|
||||||
|
Plaintext.Status.PUBKEY_REQUESTED -> return R.drawable.public_key
|
||||||
|
Plaintext.Status.DOING_PROOF_OF_WORK -> return R.drawable.ic_notification_proof_of_work
|
||||||
|
Plaintext.Status.SENT -> return R.drawable.sent
|
||||||
|
Plaintext.Status.SENT_ACKNOWLEDGED -> return R.drawable.sent_acknowledged
|
||||||
|
else -> return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
fun getStatusString(status: Plaintext.Status): Int {
|
||||||
|
when (status) {
|
||||||
|
Plaintext.Status.RECEIVED -> return R.string.status_received
|
||||||
|
Plaintext.Status.DRAFT -> return R.string.status_draft
|
||||||
|
Plaintext.Status.PUBKEY_REQUESTED -> return R.string.status_public_key
|
||||||
|
Plaintext.Status.DOING_PROOF_OF_WORK -> return R.string.proof_of_work_title
|
||||||
|
Plaintext.Status.SENT -> return R.string.status_sent
|
||||||
|
Plaintext.Status.SENT_ACKNOWLEDGED -> return R.string.status_sent_acknowledged
|
||||||
|
else -> return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,36 +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.util;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class Constants {
|
|
||||||
public static final String PREFERENCE_WIFI_ONLY = "wifi_only";
|
|
||||||
public static final String PREFERENCE_TRUSTED_NODE = "trusted_node";
|
|
||||||
public static final String PREFERENCE_SYNC_TIMEOUT = "sync_timeout";
|
|
||||||
public static final String PREFERENCE_SERVER_POW = "server_pow";
|
|
||||||
public static final String PREFERENCE_FULL_NODE = "full_node";
|
|
||||||
public static final String PREFERENCE_REQUEST_ACK = "request_acknowledgments";
|
|
||||||
public static final String PREFERENCE_POW_AVERAGE = "average_pow_time_ms";
|
|
||||||
public static final String PREFERENCE_POW_COUNT = "pow_count";
|
|
||||||
|
|
||||||
public static final String BITMESSAGE_URL_SCHEMA = "bitmessage:";
|
|
||||||
public static final Pattern BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b");
|
|
||||||
}
|
|
37
app/src/main/java/ch/dissem/apps/abit/util/Constants.kt
Normal file
37
app/src/main/java/ch/dissem/apps/abit/util/Constants.kt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
object Constants {
|
||||||
|
const val PREFERENCE_WIFI_ONLY = "wifi_only"
|
||||||
|
const val PREFERENCE_TRUSTED_NODE = "trusted_node"
|
||||||
|
const val PREFERENCE_SYNC_TIMEOUT = "sync_timeout"
|
||||||
|
const val PREFERENCE_SERVER_POW = "server_pow"
|
||||||
|
const val PREFERENCE_FULL_NODE = "full_node"
|
||||||
|
const val PREFERENCE_REQUEST_ACK = "request_acknowledgments"
|
||||||
|
const val PREFERENCE_POW_AVERAGE = "average_pow_time_ms"
|
||||||
|
const val PREFERENCE_POW_COUNT = "pow_count"
|
||||||
|
|
||||||
|
const val BITMESSAGE_URL_SCHEMA = "bitmessage:"
|
||||||
|
|
||||||
|
val BITMESSAGE_ADDRESS_PATTERN = Pattern.compile("\\bBM-[a-zA-Z0-9]+\\b")
|
||||||
|
}
|
@ -1,108 +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.util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
import com.google.zxing.MultiFormatWriter;
|
|
||||||
import com.google.zxing.WriterException;
|
|
||||||
import com.google.zxing.common.BitMatrix;
|
|
||||||
import com.mikepenz.iconics.IconicsDrawable;
|
|
||||||
import com.mikepenz.iconics.typeface.IIcon;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.Identicon;
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
|
|
||||||
import static android.graphics.Color.BLACK;
|
|
||||||
import static android.graphics.Color.WHITE;
|
|
||||||
import static android.util.Base64.NO_WRAP;
|
|
||||||
import static android.util.Base64.URL_SAFE;
|
|
||||||
import static ch.dissem.apps.abit.util.Constants.BITMESSAGE_URL_SCHEMA;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some helper methods to work with drawables.
|
|
||||||
*/
|
|
||||||
public class Drawables {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Drawables.class);
|
|
||||||
|
|
||||||
private static final int QR_CODE_SIZE = 350;
|
|
||||||
|
|
||||||
public static MenuItem addIcon(Context ctx, Menu menu, int menuItem, IIcon icon) {
|
|
||||||
MenuItem item = menu.findItem(menuItem);
|
|
||||||
item.setIcon(new IconicsDrawable(ctx, icon).colorRes(R.color.colorPrimaryDarkText).actionBar());
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap toBitmap(Identicon identicon, int size) {
|
|
||||||
return toBitmap(identicon, size, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap toBitmap(Identicon identicon, int width, int height) {
|
|
||||||
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
|
||||||
Canvas canvas = new Canvas(bitmap);
|
|
||||||
identicon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
|
||||||
identicon.draw(canvas);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bitmap qrCode(BitmessageAddress address) {
|
|
||||||
StringBuilder link = new StringBuilder();
|
|
||||||
link.append(BITMESSAGE_URL_SCHEMA);
|
|
||||||
link.append(address.getAddress());
|
|
||||||
if (address.getAlias() != null) {
|
|
||||||
link.append("?label=").append(address.getAlias());
|
|
||||||
}
|
|
||||||
if (address.getPubkey() != null) {
|
|
||||||
link.append(address.getAlias() == null ? '?' : '&');
|
|
||||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
|
||||||
address.getPubkey().writeUnencrypted(pubkey);
|
|
||||||
link.append("pubkey=").append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE | NO_WRAP));
|
|
||||||
}
|
|
||||||
BitMatrix result;
|
|
||||||
try {
|
|
||||||
result = new MultiFormatWriter().encode(link.toString(),
|
|
||||||
BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE, null);
|
|
||||||
} catch (WriterException e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int w = result.getWidth();
|
|
||||||
int h = result.getHeight();
|
|
||||||
int[] pixels = new int[w * h];
|
|
||||||
for (int y = 0; y < h; y++) {
|
|
||||||
int offset = y * w;
|
|
||||||
for (int x = 0; x < w; x++) {
|
|
||||||
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
|
|
||||||
bitmap.setPixels(pixels, 0, QR_CODE_SIZE, 0, 0, w, h);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
}
|
|
101
app/src/main/java/ch/dissem/apps/abit/util/Drawables.kt
Normal file
101
app/src/main/java/ch/dissem/apps/abit/util/Drawables.kt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color.BLACK
|
||||||
|
import android.graphics.Color.WHITE
|
||||||
|
import android.util.Base64
|
||||||
|
import android.util.Base64.NO_WRAP
|
||||||
|
import android.util.Base64.URL_SAFE
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import ch.dissem.apps.abit.Identicon
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import com.google.zxing.BarcodeFormat
|
||||||
|
import com.google.zxing.MultiFormatWriter
|
||||||
|
import com.google.zxing.WriterException
|
||||||
|
import com.google.zxing.common.BitMatrix
|
||||||
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
|
import com.mikepenz.iconics.typeface.IIcon
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some helper methods to work with drawables.
|
||||||
|
*/
|
||||||
|
object Drawables {
|
||||||
|
private val LOG = LoggerFactory.getLogger(Drawables::class.java)
|
||||||
|
|
||||||
|
private val QR_CODE_SIZE = 350
|
||||||
|
|
||||||
|
fun addIcon(ctx: Context, menu: Menu, menuItem: Int, icon: IIcon): MenuItem {
|
||||||
|
val item = menu.findItem(menuItem)
|
||||||
|
item.icon = IconicsDrawable(ctx, icon).colorRes(R.color.colorPrimaryDarkText).actionBar()
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toBitmap(identicon: Identicon, width: Int, height: Int = width): Bitmap {
|
||||||
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
identicon.setBounds(0, 0, canvas.width, canvas.height)
|
||||||
|
identicon.draw(canvas)
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun qrCode(address: BitmessageAddress?): Bitmap? {
|
||||||
|
if (address == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val link = StringBuilder()
|
||||||
|
link.append(Constants.BITMESSAGE_URL_SCHEMA)
|
||||||
|
link.append(address.address)
|
||||||
|
if (address.alias != null) {
|
||||||
|
link.append("?label=").append(address.alias)
|
||||||
|
}
|
||||||
|
if (address.pubkey != null) {
|
||||||
|
link.append(if (address.alias == null) '?' else '&')
|
||||||
|
val pubkey = ByteArrayOutputStream()
|
||||||
|
address.pubkey!!.writeUnencrypted(pubkey)
|
||||||
|
link.append("pubkey=").append(Base64.encodeToString(pubkey.toByteArray(), URL_SAFE or NO_WRAP))
|
||||||
|
}
|
||||||
|
val result: BitMatrix
|
||||||
|
try {
|
||||||
|
result = MultiFormatWriter().encode(link.toString(),
|
||||||
|
BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE, null)
|
||||||
|
} catch (e: WriterException) {
|
||||||
|
LOG.error(e.message, e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val w = result.width
|
||||||
|
val h = result.height
|
||||||
|
val pixels = IntArray(w * h)
|
||||||
|
for (y in 0 until h) {
|
||||||
|
val offset = y * w
|
||||||
|
for (x in 0 until w) {
|
||||||
|
pixels[offset + x] = if (result.get(x, y)) BLACK else WHITE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||||
|
bitmap.setPixels(pixels, 0, QR_CODE_SIZE, 0, 0, w, h)
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package ch.dissem.apps.abit.util
|
package ch.dissem.apps.abit.util
|
||||||
|
|
||||||
import android.support.annotation.DrawableRes
|
import android.support.annotation.DrawableRes
|
||||||
import android.support.design.widget.FloatingActionButton
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.MainActivity
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
import io.github.kobakei.materialfabspeeddial.FabSpeedDial
|
||||||
@ -12,7 +10,6 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDialMenu
|
|||||||
* Utilities to work with the common floating action button in the main activity
|
* Utilities to work with the common floating action button in the main activity
|
||||||
*/
|
*/
|
||||||
object FabUtils {
|
object FabUtils {
|
||||||
@JvmStatic
|
|
||||||
fun initFab(activity: MainActivity, @DrawableRes drawableRes: Int, menu: FabSpeedDialMenu): FabSpeedDial {
|
fun initFab(activity: MainActivity, @DrawableRes drawableRes: Int, menu: FabSpeedDialMenu): FabSpeedDial {
|
||||||
val fab = activity.floatingActionButton
|
val fab = activity.floatingActionButton
|
||||||
fab.removeAllOnMenuItemClickListeners()
|
fab.removeAllOnMenuItemClickListeners()
|
||||||
@ -21,7 +18,7 @@ object FabUtils {
|
|||||||
val mainFab = fab.mainFab
|
val mainFab = fab.mainFab
|
||||||
mainFab.setImageResource(drawableRes)
|
mainFab.setImageResource(drawableRes)
|
||||||
fab.setMenu(menu)
|
fab.setMenu(menu)
|
||||||
fab.addOnStateChangeListener { isOpened ->
|
fab.addOnStateChangeListener { isOpened: Boolean ->
|
||||||
if (isOpened) {
|
if (isOpened) {
|
||||||
// It will be turned 45 degrees, which makes an x out of the +
|
// It will be turned 45 degrees, which makes an x out of the +
|
||||||
mainFab.setImageResource(R.drawable.ic_action_add)
|
mainFab.setImageResource(R.drawable.ic_action_add)
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package ch.dissem.apps.abit.util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.annotation.ColorInt;
|
|
||||||
|
|
||||||
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
|
|
||||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
|
||||||
import com.mikepenz.iconics.typeface.IIcon;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to help with translating the default labels, getting label colors and so on.
|
|
||||||
*/
|
|
||||||
public class Labels {
|
|
||||||
public static String getText(Label label, Context ctx) {
|
|
||||||
return getText(label.getType(), label.toString(), ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getText(Label.Type type, String alternative, Context ctx) {
|
|
||||||
if (type == null) {
|
|
||||||
return alternative;
|
|
||||||
} else {
|
|
||||||
switch (type) {
|
|
||||||
case INBOX:
|
|
||||||
return ctx.getString(R.string.inbox);
|
|
||||||
case DRAFT:
|
|
||||||
return ctx.getString(R.string.draft);
|
|
||||||
case OUTBOX:
|
|
||||||
return ctx.getString(R.string.outbox);
|
|
||||||
case SENT:
|
|
||||||
return ctx.getString(R.string.sent);
|
|
||||||
case UNREAD:
|
|
||||||
return ctx.getString(R.string.unread);
|
|
||||||
case TRASH:
|
|
||||||
return ctx.getString(R.string.trash);
|
|
||||||
case BROADCAST:
|
|
||||||
return ctx.getString(R.string.broadcasts);
|
|
||||||
default:
|
|
||||||
return alternative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IIcon getIcon(Label label) {
|
|
||||||
if (label.getType() == null) {
|
|
||||||
return CommunityMaterial.Icon.cmd_label;
|
|
||||||
}
|
|
||||||
switch (label.getType()) {
|
|
||||||
case INBOX:
|
|
||||||
return GoogleMaterial.Icon.gmd_inbox;
|
|
||||||
case DRAFT:
|
|
||||||
return CommunityMaterial.Icon.cmd_file;
|
|
||||||
case OUTBOX:
|
|
||||||
return CommunityMaterial.Icon.cmd_inbox_arrow_up;
|
|
||||||
case SENT:
|
|
||||||
return CommunityMaterial.Icon.cmd_send;
|
|
||||||
case BROADCAST:
|
|
||||||
return CommunityMaterial.Icon.cmd_rss;
|
|
||||||
case UNREAD:
|
|
||||||
return GoogleMaterial.Icon.gmd_markunread_mailbox;
|
|
||||||
case TRASH:
|
|
||||||
return GoogleMaterial.Icon.gmd_delete;
|
|
||||||
default:
|
|
||||||
return CommunityMaterial.Icon.cmd_label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorInt
|
|
||||||
public static int getColor(Label label) {
|
|
||||||
if (label.getType() == null) {
|
|
||||||
return label.getColor();
|
|
||||||
}
|
|
||||||
return 0xFF000000;
|
|
||||||
}
|
|
||||||
}
|
|
47
app/src/main/java/ch/dissem/apps/abit/util/Labels.kt
Normal file
47
app/src/main/java/ch/dissem/apps/abit/util/Labels.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package ch.dissem.apps.abit.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.annotation.ColorInt
|
||||||
|
|
||||||
|
import com.mikepenz.community_material_typeface_library.CommunityMaterial
|
||||||
|
import com.mikepenz.google_material_typeface_library.GoogleMaterial
|
||||||
|
import com.mikepenz.iconics.typeface.IIcon
|
||||||
|
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to help with translating the default labels, getting label colors and so on.
|
||||||
|
*/
|
||||||
|
object Labels {
|
||||||
|
fun getText(label: Label, ctx: Context): String = getText(label.type, label.toString(), ctx)!!
|
||||||
|
|
||||||
|
fun getText(type: Label.Type?, alternative: String?, ctx: Context) = when (type) {
|
||||||
|
Label.Type.INBOX -> ctx.getString(R.string.inbox)
|
||||||
|
Label.Type.DRAFT -> ctx.getString(R.string.draft)
|
||||||
|
Label.Type.OUTBOX -> ctx.getString(R.string.outbox)
|
||||||
|
Label.Type.SENT -> ctx.getString(R.string.sent)
|
||||||
|
Label.Type.UNREAD -> ctx.getString(R.string.unread)
|
||||||
|
Label.Type.TRASH -> ctx.getString(R.string.trash)
|
||||||
|
Label.Type.BROADCAST -> ctx.getString(R.string.broadcasts)
|
||||||
|
else -> alternative
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIcon(label: Label): IIcon = when (label.type) {
|
||||||
|
Label.Type.INBOX -> GoogleMaterial.Icon.gmd_inbox
|
||||||
|
Label.Type.DRAFT -> CommunityMaterial.Icon.cmd_file
|
||||||
|
Label.Type.OUTBOX -> CommunityMaterial.Icon.cmd_inbox_arrow_up
|
||||||
|
Label.Type.SENT -> CommunityMaterial.Icon.cmd_send
|
||||||
|
Label.Type.BROADCAST -> CommunityMaterial.Icon.cmd_rss
|
||||||
|
Label.Type.UNREAD -> GoogleMaterial.Icon.gmd_markunread_mailbox
|
||||||
|
Label.Type.TRASH -> GoogleMaterial.Icon.gmd_delete
|
||||||
|
else -> CommunityMaterial.Icon.cmd_label
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
fun getColor(label: Label): Int {
|
||||||
|
return if (label.type == null) {
|
||||||
|
label.color
|
||||||
|
} else 0xFF000000.toInt()
|
||||||
|
}
|
||||||
|
}
|
@ -8,19 +8,14 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.support.annotation.RequiresApi
|
import android.support.annotation.RequiresApi
|
||||||
import ch.dissem.apps.abit.MainActivity.updateNodeSwitch
|
import ch.dissem.apps.abit.MainActivity
|
||||||
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
|
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
|
||||||
import ch.dissem.apps.abit.service.BitmessageService
|
import ch.dissem.apps.abit.service.BitmessageService
|
||||||
import ch.dissem.apps.abit.service.StartupNodeOnWifiService
|
import ch.dissem.apps.abit.service.StartupNodeOnWifiService
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by chrigu on 18.08.17.
|
|
||||||
*/
|
|
||||||
object NetworkUtils {
|
object NetworkUtils {
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@JvmOverloads
|
|
||||||
fun enableNode(ctx: Context, ask: Boolean = true) {
|
fun enableNode(ctx: Context, ask: Boolean = true) {
|
||||||
Preferences.setFullNodeActive(ctx, true)
|
Preferences.setFullNodeActive(ctx, true)
|
||||||
if (Preferences.isWifiOnly(ctx)) {
|
if (Preferences.isWifiOnly(ctx)) {
|
||||||
@ -29,7 +24,7 @@ object NetworkUtils {
|
|||||||
scheduleNodeStart(ctx)
|
scheduleNodeStart(ctx)
|
||||||
} else {
|
} else {
|
||||||
ctx.startService(Intent(ctx, BitmessageService::class.java))
|
ctx.startService(Intent(ctx, BitmessageService::class.java))
|
||||||
updateNodeSwitch()
|
MainActivity.updateNodeSwitch()
|
||||||
}
|
}
|
||||||
} else if (ask) {
|
} else if (ask) {
|
||||||
val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java)
|
val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java)
|
||||||
@ -43,23 +38,22 @@ object NetworkUtils {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.startService(Intent(ctx, BitmessageService::class.java))
|
ctx.startService(Intent(ctx, BitmessageService::class.java))
|
||||||
updateNodeSwitch()
|
MainActivity.updateNodeSwitch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun disableNode(ctx: Context) {
|
fun disableNode(ctx: Context) {
|
||||||
Preferences.setFullNodeActive(ctx, false)
|
Preferences.setFullNodeActive(ctx, false)
|
||||||
ctx.stopService(Intent(ctx, BitmessageService::class.java))
|
ctx.stopService(Intent(ctx, BitmessageService::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
fun scheduleNodeStart(ctx: Context) {
|
fun scheduleNodeStart(ctx: Context) {
|
||||||
val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java)
|
val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java)
|
||||||
val builder = JobInfo.Builder(0, serviceComponent)
|
val builder = JobInfo.Builder(0, serviceComponent)
|
||||||
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
|
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
|
||||||
|
builder.setBackoffCriteria(0L, JobInfo.BACKOFF_POLICY_LINEAR)
|
||||||
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
||||||
jobScheduler.schedule(builder.build());
|
jobScheduler.schedule(builder.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ object PowStats {
|
|||||||
var averagePowUnitTime = 0L
|
var averagePowUnitTime = 0L
|
||||||
var powCount = 0L
|
var powCount = 0L
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getExpectedPowTimeInMilliseconds(ctx: Context, target: ByteArray): Long {
|
fun getExpectedPowTimeInMilliseconds(ctx: Context, target: ByteArray): Long {
|
||||||
if (averagePowUnitTime == 0L) {
|
if (averagePowUnitTime == 0L) {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
@ -27,7 +26,6 @@ object PowStats {
|
|||||||
return (BigInteger.valueOf(averagePowUnitTime) * BigInteger(target) / TWO_POW_64).toLong()
|
return (BigInteger.valueOf(averagePowUnitTime) * BigInteger(target) / TWO_POW_64).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun addPow(ctx: Context, time: Long, target: ByteArray) {
|
fun addPow(ctx: Context, time: Long, target: ByteArray) {
|
||||||
val targetBigInt = BigInteger(target)
|
val targetBigInt = BigInteger(target)
|
||||||
val powCountBefore = BigInteger.valueOf(powCount)
|
val powCountBefore = BigInteger.valueOf(powCount)
|
||||||
|
@ -21,7 +21,11 @@ import android.preference.PreferenceManager
|
|||||||
import ch.dissem.apps.abit.R
|
import ch.dissem.apps.abit.R
|
||||||
import ch.dissem.apps.abit.listener.WifiReceiver
|
import ch.dissem.apps.abit.listener.WifiReceiver
|
||||||
import ch.dissem.apps.abit.notification.ErrorNotification
|
import ch.dissem.apps.abit.notification.ErrorNotification
|
||||||
import ch.dissem.apps.abit.util.Constants.*
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE
|
||||||
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK
|
||||||
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT
|
||||||
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE
|
||||||
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -33,7 +37,6 @@ import java.net.InetAddress
|
|||||||
object Preferences {
|
object Preferences {
|
||||||
private val LOG = LoggerFactory.getLogger(Preferences::class.java)
|
private val LOG = LoggerFactory.getLogger(Preferences::class.java)
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun useTrustedNode(ctx: Context): Boolean {
|
fun useTrustedNode(ctx: Context): Boolean {
|
||||||
val trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return false
|
val trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return false
|
||||||
return trustedNode.trim { it <= ' ' }.isNotEmpty()
|
return trustedNode.trim { it <= ' ' }.isNotEmpty()
|
||||||
@ -43,7 +46,6 @@ object Preferences {
|
|||||||
* Warning, this method might do a network call and therefore can't be called from
|
* Warning, this method might do a network call and therefore can't be called from
|
||||||
* the UI thread.
|
* the UI thread.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getTrustedNode(ctx: Context): InetAddress? {
|
fun getTrustedNode(ctx: Context): InetAddress? {
|
||||||
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return null
|
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return null
|
||||||
@ -57,7 +59,6 @@ object Preferences {
|
|||||||
return InetAddress.getByName(trustedNode)
|
return InetAddress.getByName(trustedNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getTrustedNodePort(ctx: Context): Int {
|
fun getTrustedNodePort(ctx: Context): Int {
|
||||||
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return 8444
|
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return 8444
|
||||||
trustedNode = trustedNode.trim { it <= ' ' }
|
trustedNode = trustedNode.trim { it <= ' ' }
|
||||||
@ -69,14 +70,13 @@ object Preferences {
|
|||||||
return Integer.parseInt(portString)
|
return Integer.parseInt(portString)
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
ErrorNotification(ctx)
|
ErrorNotification(ctx)
|
||||||
.setError(R.string.error_invalid_sync_port, portString)
|
.setError(R.string.error_invalid_sync_port, portString)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 8444
|
return 8444
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getTimeoutInSeconds(ctx: Context): Long {
|
fun getTimeoutInSeconds(ctx: Context): Long {
|
||||||
val preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT) ?: return 120
|
val preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT) ?: return 120
|
||||||
return preference.toLong()
|
return preference.toLong()
|
||||||
@ -88,53 +88,40 @@ object Preferences {
|
|||||||
return preferences.getString(name, null)
|
return preferences.getString(name, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
fun isConnectionAllowed(ctx: Context) = !isWifiOnly(ctx) || !WifiReceiver.isConnectedToMeteredNetwork(ctx)
|
||||||
fun isConnectionAllowed(ctx: Context): Boolean {
|
|
||||||
return !isWifiOnly(ctx) || !WifiReceiver.isConnectedToMeteredNetwork(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun isWifiOnly(ctx: Context): Boolean {
|
fun isWifiOnly(ctx: Context): Boolean {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
return preferences.getBoolean(PREFERENCE_WIFI_ONLY, true)
|
return preferences.getBoolean(PREFERENCE_WIFI_ONLY, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setWifiOnly(ctx: Context, status: Boolean) {
|
fun setWifiOnly(ctx: Context, status: Boolean) {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
preferences.edit().putBoolean(PREFERENCE_WIFI_ONLY, status).apply()
|
preferences.edit().putBoolean(PREFERENCE_WIFI_ONLY, status).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun isFullNodeActive(ctx: Context): Boolean {
|
fun isFullNodeActive(ctx: Context): Boolean {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
return preferences.getBoolean(PREFERENCE_FULL_NODE, false)
|
return preferences.getBoolean(PREFERENCE_FULL_NODE, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setFullNodeActive(ctx: Context, status: Boolean) {
|
fun setFullNodeActive(ctx: Context, status: Boolean) {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
preferences.edit().putBoolean(PREFERENCE_FULL_NODE, status).apply()
|
preferences.edit().putBoolean(PREFERENCE_FULL_NODE, status).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
fun getExportDirectory(ctx: Context) = File(ctx.filesDir, "exports")
|
||||||
fun getExportDirectory(ctx: Context): File {
|
|
||||||
return File(ctx.filesDir, "exports")
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun requestAcknowledgements(ctx: Context): Boolean {
|
fun requestAcknowledgements(ctx: Context): Boolean {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
return preferences.getBoolean(PREFERENCE_REQUEST_ACK, true)
|
return preferences.getBoolean(PREFERENCE_REQUEST_ACK, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun setRequestAcknowledgements(ctx: Context, status: Boolean) {
|
fun setRequestAcknowledgements(ctx: Context, status: Boolean) {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
preferences.edit().putBoolean(PREFERENCE_REQUEST_ACK, status).apply()
|
preferences.edit().putBoolean(PREFERENCE_REQUEST_ACK, status).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun cleanupExportDirectory(ctx: Context) {
|
fun cleanupExportDirectory(ctx: Context) {
|
||||||
val exportDirectory = getExportDirectory(ctx)
|
val exportDirectory = getExportDirectory(ctx)
|
||||||
if (exportDirectory.exists()) {
|
if (exportDirectory.exists()) {
|
||||||
|
@ -14,29 +14,27 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.apps.abit.util;
|
package ch.dissem.apps.abit.util
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Christian Basler
|
* @author Christian Basler
|
||||||
*/
|
*/
|
||||||
public class Strings {
|
object Strings {
|
||||||
private final static Pattern WHITESPACES = Pattern.compile("\\s+");
|
private val WHITESPACES = Pattern.compile("\\s+")
|
||||||
|
|
||||||
private static CharSequence trim(CharSequence string, int length) {
|
private fun trim(string: CharSequence?, length: Int) = if (string == null) {
|
||||||
if (string.length() <= length) {
|
""
|
||||||
return string;
|
} else if (string.length <= length) {
|
||||||
} else {
|
string
|
||||||
return string.subSequence(0, length);
|
} else {
|
||||||
}
|
string.subSequence(0, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trime the string to 200 characters and normalizes all whitespaces by replacing any sequence
|
* Trim the string to 200 characters and normalizes all whitespaces by replacing any sequence
|
||||||
* of whitespace characters with a single space character.
|
* of whitespace characters with a single space character.
|
||||||
*/
|
*/
|
||||||
public static String prepareMessageExtract(CharSequence string) {
|
fun prepareMessageExtract(string: CharSequence?) = WHITESPACES.matcher(trim(string, 200)).replaceAll(" ")
|
||||||
return WHITESPACES.matcher(trim(string, 200)).replaceAll(" ");
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,41 +0,0 @@
|
|||||||
package ch.dissem.apps.abit.util;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQLite has no UUID data type, and UUIDs are therefore best saved as BINARY[16]. This class
|
|
||||||
* takes care of conversion between byte[16] and UUID.
|
|
||||||
* <p>
|
|
||||||
* Thanks to Brice Roncace on
|
|
||||||
* <a href="http://stackoverflow.com/questions/17893609/convert-uuid-to-byte-that-works-when-using-uuid-nameuuidfrombytesb">
|
|
||||||
* Stack Overflow
|
|
||||||
* </a>
|
|
||||||
* for providing the UUID <-> byte[] conversions.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class UuidUtils {
|
|
||||||
/**
|
|
||||||
* @param bytes that represent a UUID, or null for a random UUID
|
|
||||||
* @return the UUID from the given bytes, or a random UUID if bytes is null.
|
|
||||||
*/
|
|
||||||
public static UUID asUuid(byte[] bytes) {
|
|
||||||
if (bytes == null) {
|
|
||||||
return UUID.randomUUID();
|
|
||||||
}
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(bytes);
|
|
||||||
long firstLong = bb.getLong();
|
|
||||||
long secondLong = bb.getLong();
|
|
||||||
return new UUID(firstLong, secondLong);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] asBytes(UUID uuid) {
|
|
||||||
if (uuid == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
|
||||||
bb.putLong(uuid.getMostSignificantBits());
|
|
||||||
bb.putLong(uuid.getLeastSignificantBits());
|
|
||||||
return bb.array();
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user