diff --git a/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java b/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java deleted file mode 100644 index aeaed74..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.java +++ /dev/null @@ -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 extends ListFragment implements ListHolder { - /** - * 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 dummyCallbacks = - new ListSelectionListener() { - @Override - public void onItemSelected(Object item) { - // NO OP - } - }; - /** - * The fragment's current callback object, which is notified of list item - * clicks. - */ - private ListSelectionListener callbacks = dummyCallbacks; - /** - * The current activated item position. Only used on tablets. - */ - private int activatedPosition = ListView.INVALID_POSITION; - 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; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.kt new file mode 100644 index 0000000..85ffc16 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/AbstractItemListFragment.kt @@ -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 : ListFragment(), ListHolder { + /** + * The fragment's current callback object, which is notified of list item + * clicks. + */ + @Suppress("UNCHECKED_CAST") + private var callbacks: ListSelectionListener = DummyCallback as ListSelectionListener + /** + * 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 + } 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 + } + + 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 { + 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" + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.java b/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.kt similarity index 71% rename from app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.java rename to app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.kt index a00e613..cad11ec 100644 --- a/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/AddressDetailActivity.kt @@ -14,25 +14,25 @@ * limitations under the License. */ -package ch.dissem.apps.abit; +package ch.dissem.apps.abit -import android.os.Bundle; +import android.os.Bundle /** * An activity representing a single Subscription detail screen. This * activity is only used on handset devices. On tablet-size devices, * item details are presented side-by-side with a list of items - * in a {@link MainActivity}. - *

+ * in a [MainActivity]. + * + * * This activity is mostly just a 'shell' activity containing nothing - * more than a {@link AddressDetailFragment}. + * more than a [AddressDetailFragment]. */ -public class AddressDetailActivity extends DetailActivity { +class AddressDetailActivity : DetailActivity() { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) // savedInstanceState is non-null when there is fragment state // saved from previous configurations of this activity @@ -42,18 +42,18 @@ public class AddressDetailActivity extends DetailActivity { // For more information, see the Fragments API guide at: // // http://developer.android.com/guide/components/fragments.html - // + if (savedInstanceState == null) { // Create the detail fragment and add it to the activity // using a fragment transaction. - Bundle arguments = new Bundle(); + val arguments = Bundle() arguments.putSerializable(AddressDetailFragment.ARG_ITEM, - getIntent().getSerializableExtra(AddressDetailFragment.ARG_ITEM)); - AddressDetailFragment fragment = new AddressDetailFragment(); - fragment.setArguments(arguments); - getSupportFragmentManager().beginTransaction() + intent.getSerializableExtra(AddressDetailFragment.ARG_ITEM)) + val fragment = AddressDetailFragment() + fragment.arguments = arguments + supportFragmentManager.beginTransaction() .add(R.id.content, fragment) - .commit(); + .commit() } } } diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java deleted file mode 100644 index 791e1b2..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt new file mode 100644 index 0000000..991919f --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.apps.abit + +import android.app.AlertDialog +import android.content.Intent +import android.os.Bundle +import android.support.v4.app.Fragment +import android.text.Editable +import android.text.TextWatcher +import android.view.* +import android.widget.Toast +import ch.dissem.apps.abit.service.Singleton +import ch.dissem.apps.abit.util.Drawables +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.wif.WifExporter +import com.mikepenz.community_material_typeface_library.CommunityMaterial +import com.mikepenz.google_material_typeface_library.GoogleMaterial +import kotlinx.android.synthetic.main.fragment_address_detail.* + + +/** + * A fragment representing a single Message detail screen. + * This fragment is either contained in a [MainActivity] + * in two-pane mode (on tablets) or a [MessageDetailActivity] + * on handsets. + */ +class AddressDetailFragment : Fragment() { + + /** + * The content this fragment is presenting. + */ + private var item: BitmessageAddress? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (arguments.containsKey(ARG_ITEM)) { + item = arguments.getSerializable(ARG_ITEM) as BitmessageAddress + } + setHasOptionsMenu(true) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.address, menu) + + val activity = activity + Drawables.addIcon(activity, menu, R.id.write_message, GoogleMaterial.Icon.gmd_mail) + Drawables.addIcon(activity, menu, R.id.share, GoogleMaterial.Icon.gmd_share) + Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete) + Drawables.addIcon(activity, menu, R.id.export, + CommunityMaterial.Icon.cmd_export).isVisible = item != null && item!!.privateKey != null + + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { + val item = item ?: return false + val ctx = activity + when (menuItem.itemId) { + R.id.write_message -> { + val identity = Singleton.getIdentity(ctx) + if (identity == null) { + Toast.makeText(ctx, R.string.no_identity_warning, Toast.LENGTH_LONG).show() + } else { + val intent = Intent(ctx, ComposeMessageActivity::class.java) + intent.putExtra(ComposeMessageActivity.EXTRA_IDENTITY, identity) + intent.putExtra(ComposeMessageActivity.EXTRA_RECIPIENT, item) + startActivity(intent) + } + return true + } + R.id.delete -> { + val warning = if (item.privateKey != null) + R.string.delete_identity_warning + else + R.string.delete_contact_warning + AlertDialog.Builder(ctx) + .setMessage(warning) + .setPositiveButton(android.R.string.yes) { _, _ -> + Singleton.getAddressRepository(ctx).remove(item) + val mainActivity = MainActivity.getInstance() + if (item.privateKey != null && mainActivity != null) { + mainActivity.removeIdentityEntry(item) + } + this.item = null + ctx.onBackPressed() + } + .setNegativeButton(android.R.string.no, null) + .show() + return true + } + R.id.export -> { + AlertDialog.Builder(ctx) + .setMessage(R.string.confirm_export) + .setPositiveButton(android.R.string.yes) { _, _ -> + val shareIntent = Intent(Intent.ACTION_SEND) + shareIntent.type = "text/plain" + shareIntent.putExtra(Intent.EXTRA_TITLE, item.toString() + EXPORT_POSTFIX) + val exporter = WifExporter(Singleton + .getBitmessageContext(ctx)) + exporter.addIdentity(item) + shareIntent.putExtra(Intent.EXTRA_TEXT, exporter.toString()) + startActivity(Intent.createChooser(shareIntent, null)) + } + .setNegativeButton(android.R.string.no, null) + .show() + return true + } + R.id.share -> { + val shareIntent = Intent(Intent.ACTION_SEND) + shareIntent.type = "text/plain" + shareIntent.putExtra(Intent.EXTRA_TEXT, item.address) + startActivity(Intent.createChooser(shareIntent, null)) + return true + } + else -> return false + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View + = inflater.inflate(R.layout.fragment_address_detail, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + // Show the dummy content as text in a TextView. + item?.let { item -> + val activity = activity + when { + item.isChan -> activity.setTitle(R.string.title_chan_detail) + item.privateKey != null -> activity.setTitle(R.string.title_identity_detail) + item.isSubscribed -> activity.setTitle(R.string.title_subscription_detail) + else -> activity.setTitle(R.string.title_contact_detail) + } + + avatar.setImageDrawable(Identicon(item)) + name.setText(item.toString()) + name.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + // Nothing to do + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + // Nothing to do + } + + override fun afterTextChanged(s: Editable) { + item.alias = s.toString() + } + }) + address.text = item.address + address.isSelected = true + stream_number.text = getString(R.string.stream_number, item.stream) + if (item.privateKey == null) { + active.isChecked = item.isSubscribed + active.setOnCheckedChangeListener { _, checked -> item.isSubscribed = checked } + + if (item.pubkey == null) { + pubkey_available.alpha = 0.3f + pubkey_available_desc.setText(R.string.pubkey_not_available) + } + } else { + active.visibility = View.GONE + pubkey_available.visibility = View.GONE + pubkey_available_desc.visibility = View.GONE + } + + // QR code + qr_code.setImageBitmap(Drawables.qrCode(item)) + } + } + + override fun onPause() { + item?.let { item -> + Singleton.getAddressRepository(context).save(item) + val mainActivity = MainActivity.getInstance() + if (mainActivity != null && item.privateKey != null) { + mainActivity.updateIdentityEntry(item) + } + } + super.onPause() + } + + companion object { + /** + * The fragment argument representing the item ID that this fragment + * represents. + */ + val ARG_ITEM = "item" + val EXPORT_POSTFIX = ".keys.dat" + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.java b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.java deleted file mode 100644 index f30b4ca..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.java +++ /dev/null @@ -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 { - private ArrayAdapter adapter; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - adapter = new ArrayAdapter( - getActivity(), - R.layout.subscription_row, - R.id.name, - new LinkedList()) { - @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() { - @Override - protected Void doInBackground(Void... params) { - List 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; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt new file mode 100644 index 0000000..c34cbee --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/AddressListFragment.kt @@ -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() { + private lateinit var adapter: ArrayAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + adapter = object : ArrayAdapter( + 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 + ) +} diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.java b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.java deleted file mode 100644 index 1cde1b0..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt new file mode 100644 index 0000000..49e0bcd --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageActivity.kt @@ -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 + } + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.java b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.java deleted file mode 100644 index 7a57892..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.java +++ /dev/null @@ -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 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(); - } -} - diff --git a/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.kt new file mode 100644 index 0000000..b839bed --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ComposeMessageFragment.kt @@ -0,0 +1,222 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.apps.abit + +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.* +import android.widget.AdapterView +import android.widget.Toast +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_BROADCAST +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_CONTENT +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_ENCODING +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_IDENTITY +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_PARENT +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_RECIPIENT +import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_SUBJECT +import ch.dissem.apps.abit.adapter.ContactAdapter +import ch.dissem.apps.abit.dialog.SelectEncodingDialogFragment +import ch.dissem.apps.abit.service.Singleton +import ch.dissem.apps.abit.util.Preferences +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import kotlinx.android.synthetic.main.fragment_compose_message.* + +/** + * Compose a new message. + */ +class ComposeMessageFragment : Fragment() { + private lateinit var identity: BitmessageAddress + private var recipient: BitmessageAddress? = null + private var subject: String = "" + private var content: String = "" + + private var broadcast: Boolean = false + private var encoding: Plaintext.Encoding = Plaintext.Encoding.SIMPLE + private var parent: Plaintext? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (arguments != null) { + var id = arguments.getSerializable(EXTRA_IDENTITY) as? BitmessageAddress + if (context != null && (id == null || id.privateKey == null)) { + id = Singleton.getIdentity(context) + } + if (id?.privateKey != null) { + identity = id + } else { + throw IllegalStateException("No identity set for ComposeMessageFragment") + } + broadcast = arguments.getBoolean(EXTRA_BROADCAST, false) + if (arguments.containsKey(EXTRA_RECIPIENT)) { + recipient = arguments.getSerializable(EXTRA_RECIPIENT) as BitmessageAddress + } + if (arguments.containsKey(EXTRA_SUBJECT)) { + subject = arguments.getString(EXTRA_SUBJECT) + } + if (arguments.containsKey(EXTRA_CONTENT)) { + content = arguments.getString(EXTRA_CONTENT) + } + encoding = arguments.getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?: Plaintext.Encoding.SIMPLE + + if (arguments.containsKey(EXTRA_PARENT)) { + parent = arguments.getSerializable(EXTRA_PARENT) as Plaintext + } + } else { + throw IllegalStateException("No identity set for ComposeMessageFragment") + } + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View = + inflater.inflate(R.layout.fragment_compose_message, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (broadcast) { + recipient_input.visibility = View.GONE + } else { + val adapter = ContactAdapter(context) + recipient_input.setAdapter(adapter) + recipient_input.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos, _ -> adapter.getItem(pos) } + recipient_input.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { + recipient = adapter.getItem(position) + } + + override fun onNothingSelected(parent: AdapterView<*>) { + // leave current selection + } + } + if (recipient != null) { + recipient_input.setText(recipient.toString()) + } + } + subject_input.setText(subject) + body_input.setText(content) + + if (recipient == null) { + recipient_input.requestFocus() + } else if (subject.isEmpty()) { + subject_input.requestFocus() + } else { + body_input.requestFocus() + body_input.setSelection(0) + } + } + + override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater) { + inflater.inflate(R.menu.compose, menu) + super.onCreateOptionsMenu(menu, inflater) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.send -> { + send() + return true + } + R.id.select_encoding -> { + val encodingDialog = SelectEncodingDialogFragment() + val args = Bundle() + args.putSerializable(EXTRA_ENCODING, encoding) + encodingDialog.arguments = args + encodingDialog.setTargetFragment(this, 0) + encodingDialog.show(fragmentManager, "select encoding dialog") + return true + } + else -> return super.onOptionsItemSelected(item) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == 0 && data != null && resultCode == RESULT_OK) { + encoding = data.getSerializableExtra(EXTRA_ENCODING) as Plaintext.Encoding + } else { + super.onActivityResult(requestCode, resultCode, data) + } + } + + private fun send() { + val builder: Plaintext.Builder + val bmc = Singleton.getBitmessageContext(context) + if (broadcast) { + builder = Plaintext.Builder(BROADCAST).from(identity) + } else { + val inputString = recipient_input.text.toString() + if (recipient == null || recipient?.toString() != inputString) { + try { + recipient = BitmessageAddress(inputString) + } catch (e: Exception) { + val contacts = Singleton.getAddressRepository(context).getContacts() + for (contact in contacts) { + if (inputString.equals(contact.alias, ignoreCase = true)) { + recipient = contact + if (inputString == contact.alias) + break + } + } + } + + } + if (recipient == null) { + Toast.makeText(context, R.string.error_msg_recipient_missing, Toast.LENGTH_LONG).show() + return + } + builder = Plaintext.Builder(MSG) + .from(identity) + .to(recipient) + } + if (!Preferences.requestAcknowledgements(context)) { + builder.preventAck() + } + when (encoding) { + Plaintext.Encoding.SIMPLE -> builder.message( + subject_input.text.toString(), + body_input.text.toString() + ) + Plaintext.Encoding.EXTENDED -> builder.message( + Message.Builder() + .subject(subject_input.text.toString()) + .body(body_input.text.toString()) + .addParent(parent) + .build() + ) + else -> { + Toast.makeText( + context, + context.getString(R.string.error_unsupported_encoding, encoding), + Toast.LENGTH_LONG + ).show() + builder.message( + subject_input.text.toString(), + body_input.text.toString() + ) + } + } + bmc.send(builder.build()) + activity.finish() + } +} + diff --git a/app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.java b/app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.java deleted file mode 100644 index c493a0c..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.java +++ /dev/null @@ -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]; - } - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.kt b/app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.kt new file mode 100644 index 0000000..9d9dac4 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/CreateAddressActivity.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.apps.abit + +import android.app.Activity +import android.net.Uri +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.util.Base64 +import android.util.Base64.URL_SAFE +import android.widget.Button +import android.widget.EditText +import android.widget.Switch +import android.widget.TextView +import ch.dissem.apps.abit.service.Singleton +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.V2Pubkey +import ch.dissem.bitmessage.entity.payload.V3Pubkey +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.util.regex.Pattern + +class CreateAddressActivity : AppCompatActivity() { + private var pubkeyBytes: ByteArray? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val uri = intent.data + if (uri != null) + setContentView(R.layout.activity_open_bitmessage_link) + else + setContentView(R.layout.activity_create_bitmessage_address) + + val address = findViewById(R.id.address) as TextView + val label = findViewById(R.id.label) as EditText + val subscribe = findViewById(R.id.subscribe) as Switch + + if (uri != null) { + val addressText = getAddress(uri) + val parameters = getParameters(uri) + for (parameter in parameters) { + val matcher = KEY_VALUE_PATTERN.matcher(parameter) + if (matcher.find()) { + val key = matcher.group(1).toLowerCase() + val value = matcher.group(2) + when (key) { + "label" -> label.setText(value.trim { it <= ' ' }) + "action" -> subscribe.isChecked = value.trim { it <= ' ' }.equals("subscribe", ignoreCase = true) + "pubkey" -> pubkeyBytes = Base64.decode(value, URL_SAFE) + else -> LOG.debug("Unknown attribute: $key=$value") + } + } + } + + address.text = addressText + } + + val cancel = findViewById(R.id.cancel) as Button + cancel.setOnClickListener { + setResult(Activity.RESULT_CANCELED) + finish() + } + findViewById(R.id.do_import).setOnClickListener { onOK(address, label, subscribe) } + } + + + private fun onOK(address: TextView, label: EditText, subscribe: Switch) { + val addressText = address.text.toString().trim { it <= ' ' } + try { + val bmAddress = BitmessageAddress(addressText) + bmAddress.alias = label.text.toString() + + val bmc = Singleton.getBitmessageContext(applicationContext) + bmc.addContact(bmAddress) + if (subscribe.isChecked) { + bmc.addSubscribtion(bmAddress) + } + if (pubkeyBytes != null) { + try { + val pubkeyStream = ByteArrayInputStream(pubkeyBytes) + val stream = bmAddress.stream + when (bmAddress.version.toInt()) { + 2 -> V2Pubkey.read(pubkeyStream, stream) + 3 -> V3Pubkey.read(pubkeyStream, stream) + 4 -> V4Pubkey(V3Pubkey.read(pubkeyStream, stream)) + else -> null + }?.let { bmAddress.pubkey = it } + } catch (ignore: Exception) { + } + } + + setResult(Activity.RESULT_OK) + finish() + } catch (e: RuntimeException) { + address.error = getString(R.string.error_illegal_address) + } + } + + private fun getAddress(uri: Uri): String { + val result = StringBuilder() + val schemeSpecificPart = uri.schemeSpecificPart + if (!schemeSpecificPart.startsWith("BM-")) { + result.append("BM-") + } + when { + schemeSpecificPart.contains("?") -> result.append(schemeSpecificPart.substring(0, schemeSpecificPart.indexOf('?'))) + schemeSpecificPart.contains("#") -> result.append(schemeSpecificPart.substring(0, schemeSpecificPart.indexOf('#'))) + else -> result.append(schemeSpecificPart) + } + return result.toString() + } + + private fun getParameters(uri: Uri): Array { + 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]+)=(.*)$") + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/DetailActivity.java b/app/src/main/java/ch/dissem/apps/abit/DetailActivity.java deleted file mode 100644 index 0289b64..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/DetailActivity.java +++ /dev/null @@ -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); - } - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/DetailActivity.kt b/app/src/main/java/ch/dissem/apps/abit/DetailActivity.kt new file mode 100644 index 0000000..b6ae31b --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/DetailActivity.kt @@ -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) + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/Identicon.java b/app/src/main/java/ch/dissem/apps/abit/Identicon.java deleted file mode 100644 index a6f5fcd..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/Identicon.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt new file mode 100644 index 0000000..172c22d --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt @@ -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 + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.java b/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.java deleted file mode 100644 index c88428e..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.kt new file mode 100644 index 0000000..af9e57f --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.kt @@ -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" + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.java b/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.java deleted file mode 100644 index 725ae7d..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.java +++ /dev/null @@ -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(); - } - } - -} diff --git a/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.kt b/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.kt new file mode 100644 index 0000000..dff5c4e --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.kt @@ -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() + } + } + +} diff --git a/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.java b/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.java deleted file mode 100644 index 2e6f227..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.kt b/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.kt new file mode 100644 index 0000000..2f8ff56 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.kt @@ -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 + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/ListHolder.java b/app/src/main/java/ch/dissem/apps/abit/ListHolder.kt similarity index 75% rename from app/src/main/java/ch/dissem/apps/abit/ListHolder.java rename to app/src/main/java/ch/dissem/apps/abit/ListHolder.kt index 8b13e8d..6e57319 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ListHolder.java +++ b/app/src/main/java/ch/dissem/apps/abit/ListHolder.kt @@ -14,17 +14,17 @@ * limitations under the License. */ -package ch.dissem.apps.abit; +package ch.dissem.apps.abit /** * @author Christian Basler */ -public interface ListHolder { - void updateList(L label); +interface ListHolder { + fun updateList(label: L) - void setActivateOnItemClick(boolean activateOnItemClick); + fun setActivateOnItemClick(activateOnItemClick: Boolean) - L getCurrentLabel(); + var currentLabel: L? - boolean showPreviousList(); + fun showPreviousList(): Boolean } diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.java b/app/src/main/java/ch/dissem/apps/abit/MainActivity.java deleted file mode 100644 index f0f3a78..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.java +++ /dev/null @@ -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. - *

- * 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}. - *

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

- */ -public class MainActivity extends AppCompatActivity - implements ListSelectionListener { - 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 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 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 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 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>() { - @Override - protected List doInBackground(Void... params) { - List identities = bmc.addresses().getIdentities(); - if (identities.isEmpty()) { - // Create an initial identity - Singleton.getIdentity(MainActivity.this); - } - return identities; - } - - @Override - protected void onPostExecute(List identities) { - for (BitmessageAddress identity : identities) { - addIdentityEntry(identity); - } - } - }.execute(); - - new AsyncTask>() { - @Override - protected List