From a18ef1ac29d50d7fa68c3481db4b87057e873348 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sat, 1 Oct 2016 10:33:11 +0200 Subject: [PATCH] Refactored adding new identities - add deterministic identities - import existing identities --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 23 ++- .../apps/abit/AddressDetailFragment.java | 78 ++++---- .../apps/abit/ImportIdentitiesFragment.java | 87 +++++++++ .../apps/abit/ImportIdentityActivity.java | 56 ++++++ .../ch/dissem/apps/abit/InputWifFragment.java | 122 +++++++++++++ .../ch/dissem/apps/abit/MainActivity.java | 110 ++---------- .../abit/adapter/AddressSelectorAdapter.java | 109 +++++++++++ .../dialog/AddIdentityDialogFragment.java | 169 ++++++++++++++++++ .../DeterministicIdentityDialogFragment.java | 138 ++++++++++++++ .../main/res/drawable/ic_action_open_file.xml | 25 +++ .../dialog_add_deterministic_identity.xml | 118 ++++++++++++ .../main/res/layout/dialog_add_identity.xml | 104 +++++++++++ .../res/layout/dialog_input_passphrase.xml | 8 +- .../main/res/layout/fragment_import_input.xml | 60 +++++++ .../fragment_import_select_identities.xml | 58 ++++++ .../main/res/layout/select_identity_row.xml | 52 ++++++ app/src/main/res/menu/import_input_data.xml | 25 +++ app/src/main/res/values-de/strings.xml | 17 +- app/src/main/res/values/strings.xml | 17 +- app/src/main/res/values/styles.xml | 3 + build.gradle | 2 +- 22 files changed, 1249 insertions(+), 134 deletions(-) create mode 100644 app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/InputWifFragment.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/adapter/AddressSelectorAdapter.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/dialog/AddIdentityDialogFragment.java create mode 100644 app/src/main/java/ch/dissem/apps/abit/dialog/DeterministicIdentityDialogFragment.java create mode 100644 app/src/main/res/drawable/ic_action_open_file.xml create mode 100644 app/src/main/res/layout/dialog_add_deterministic_identity.xml create mode 100644 app/src/main/res/layout/dialog_add_identity.xml create mode 100644 app/src/main/res/layout/fragment_import_input.xml create mode 100644 app/src/main/res/layout/fragment_import_select_identities.xml create mode 100644 app/src/main/res/layout/select_identity_row.xml create mode 100644 app/src/main/res/menu/import_input_data.xml diff --git a/app/build.gradle b/app/build.gradle index 6682841..1865a08 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,9 +61,11 @@ dependencies { compile ('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.9.3@aar'){ transitive=true } + compile 'com.github.angads25:filepicker:1.0.6' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' + compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha8' } idea.module { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a868652..90f7d6f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,8 @@ android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:replace="android:allowBackup"> @@ -103,6 +104,26 @@ + + + + + + + + + + + diff --git a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java index 5c06208..08760d6 100644 --- a/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java +++ b/app/src/main/java/ch/dissem/apps/abit/AddressDetailFragment.java @@ -106,8 +106,8 @@ public class AddressDetailFragment extends Fragment { Drawables.addIcon(getActivity(), menu, R.id.share, GoogleMaterial.Icon.gmd_share); Drawables.addIcon(getActivity(), menu, R.id.delete, GoogleMaterial.Icon.gmd_delete); Drawables.addIcon(getActivity(), menu, R.id.export, - CommunityMaterial.Icon.cmd_export) - .setVisible(item != null && item.getPrivateKey() != null); + CommunityMaterial.Icon.cmd_export) + .setVisible(item != null && item.getPrivateKey() != null); super.onCreateOptionsMenu(menu, inflater); } @@ -130,41 +130,45 @@ public class AddressDetailFragment extends Fragment { 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); - item = null; - ctx.onBackPressed(); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); + .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(); + .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: { @@ -208,7 +212,7 @@ public class AddressDetailFragment extends Fragment { address.setText(item.getAddress()); address.setSelected(true); ((TextView) rootView.findViewById(R.id.stream_number)).setText(getActivity() - .getString(R.string.stream_number, item.getStream())); + .getString(R.string.stream_number, item.getStream())); if (item.getPrivateKey() == null) { Switch active = (Switch) rootView.findViewById(R.id.active); active.setChecked(item.isSubscribed()); @@ -220,12 +224,12 @@ public class AddressDetailFragment extends Fragment { }); ImageView pubkeyAvailableImg = (ImageView) rootView.findViewById(R.id - .pubkey_available); + .pubkey_available); if (item.getPubkey() == null) { pubkeyAvailableImg.setAlpha(0.3f); TextView pubkeyAvailableDesc = (TextView) rootView.findViewById(R.id - .pubkey_available_desc); + .pubkey_available_desc); pubkeyAvailableDesc.setText(R.string.pubkey_not_available); } } else { @@ -251,7 +255,7 @@ public class AddressDetailFragment extends Fragment { BitMatrix result; try { result = new MultiFormatWriter().encode(link.toString(), - BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE, null); + BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE, null); } catch (WriterException e) { LOG.error(e.getMessage(), e); return null; diff --git a/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.java b/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.java new file mode 100644 index 0000000..f3e91a3 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ImportIdentitiesFragment.java @@ -0,0 +1,87 @@ +/* + * 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 BitmessageContext bmc; + private RecyclerView recyclerView; + private LinearLayoutManager layoutManager; + private AddressSelectorAdapter adapter; + private WifImporter importer; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle + savedInstanceState) { + String wifData = getArguments().getString(WIF_DATA); + bmc = Singleton.getBitmessageContext(getActivity()); + View view = inflater.inflate(R.layout.fragment_import_select_identities, container, false); + try { + importer = new WifImporter(bmc, wifData); + adapter = new AddressSelectorAdapter(importer.getIdentities()); + layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, + false); + 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)); + } catch (IOException e) { + return super.onCreateView(inflater, container, savedInstanceState); + } + view.findViewById(R.id.finish).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + 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/ImportIdentityActivity.java b/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.java new file mode 100644 index 0000000..725ae7d --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ImportIdentityActivity.java @@ -0,0 +1,56 @@ +/* + * 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/InputWifFragment.java b/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.java new file mode 100644 index 0000000..8ccdf5d --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/InputWifFragment.java @@ -0,0 +1,122 @@ +/* + * 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 v) { + 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/MainActivity.java b/app/src/main/java/ch/dissem/apps/abit/MainActivity.java index 5233403..1cc4bea 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.java @@ -16,7 +16,6 @@ package ch.dissem.apps.abit; -import android.annotation.SuppressLint; import android.app.AlertDialog; import android.content.ComponentName; import android.content.Context; @@ -24,7 +23,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.Point; -import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.support.v4.app.Fragment; @@ -34,8 +32,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.Toast; import com.github.amlcurran.showcaseview.ShowcaseView; import com.github.amlcurran.showcaseview.targets.Target; @@ -65,6 +61,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import ch.dissem.apps.abit.dialog.AddIdentityDialogFragment; import ch.dissem.apps.abit.listener.ActionBarListener; import ch.dissem.apps.abit.listener.ListSelectionListener; import ch.dissem.apps.abit.service.BitmessageService; @@ -75,7 +72,6 @@ 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.payload.Pubkey; import ch.dissem.bitmessage.entity.valueobject.Label; import static ch.dissem.apps.abit.service.BitmessageService.isRunning; @@ -143,6 +139,7 @@ public class MainActivity extends AppCompatActivity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + instance = new WeakReference<>(this); bmc = Singleton.getBitmessageContext(this); List