diff --git a/briar-android/build.gradle b/briar-android/build.gradle index e0463f369..0a61ddefa 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -126,6 +126,9 @@ dependencies { } implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx + // prototype + implementation "com.leinardi.android:speed-dial:2.0.0" + annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion" diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 7e3f9f712..7c9266fd7 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> @@ -29,7 +30,8 @@ android:label="@string/app_name" android:logo="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/BriarTheme"> + android:theme="@style/BriarTheme" + tools:ignore="GoogleAppIndexingWarning"> + + + + + + + + + diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 18527125a..064c8d13a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -15,6 +15,8 @@ import org.briarproject.briar.android.blog.ReblogFragment; import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity; +import org.briarproject.briar.android.contact.ContactLinkInputActivity; +import org.briarproject.briar.android.contact.ContactLinkOutputActivity; import org.briarproject.briar.android.contact.ContactListFragment; import org.briarproject.briar.android.contact.ContactModule; import org.briarproject.briar.android.conversation.AliasDialogFragment; @@ -171,6 +173,9 @@ public interface ActivityComponent { void inject(UnlockActivity activity); + void inject(ContactLinkOutputActivity activity); + void inject(ContactLinkInputActivity activity); + // Fragments void inject(AuthorNameFragment fragment); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputActivity.java new file mode 100644 index 000000000..bf0f0255b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkInputActivity.java @@ -0,0 +1,128 @@ +package org.briarproject.briar.android.contact; + +import android.content.ClipboardManager; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.MenuItem; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.activity.BriarActivity; + +import javax.annotation.Nullable; + +import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN; +import static android.widget.Toast.LENGTH_SHORT; +import static java.util.Objects.requireNonNull; + +public class ContactLinkInputActivity extends BriarActivity + implements TextWatcher { + + private ClipboardManager clipboard; + private EditText linkInput; + private Button pasteButton; + private EditText contactNameInput; + private Button addButton; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + setContentView(R.layout.activity_contact_link_input); + + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + setTitle("Enter contact link"); + + clipboard = (ClipboardManager) requireNonNull( + getSystemService(CLIPBOARD_SERVICE)); + + linkInput = findViewById(R.id.linkInput); + linkInput.addTextChangedListener(this); + + pasteButton = findViewById(R.id.pasteButton); + pasteButton.setOnClickListener(v -> { + linkInput + .setText(clipboard.getPrimaryClip().getItemAt(0).getText()); + }); + + contactNameInput = findViewById(R.id.contactNameInput); + contactNameInput.addTextChangedListener(this); + + addButton = findViewById(R.id.addButton); + addButton.setOnClickListener(v -> { + Toast.makeText(this, + "Contact " + contactNameInput.getText() + " requested", + LENGTH_SHORT).show(); + }); + } + + @Override + public void onResume() { + super.onResume(); + if (hasLinkInClipboard()) pasteButton.setEnabled(true); + else pasteButton.setEnabled(false); + } + + private boolean hasLinkInClipboard() { + return clipboard.hasPrimaryClip() && + clipboard.getPrimaryClip().getDescription() + .hasMimeType(MIMETYPE_TEXT_PLAIN) && + clipboard.getPrimaryClip().getItemCount() > 0; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + updateAddButtonState(); + } + + @Override + public void afterTextChanged(Editable s) { + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private boolean isBriarLink(CharSequence s) { + Log.e("TEST", s.toString()); + // briar://pfmrkyclibynikzg + Log.e("TEST", "'" + s.subSequence(0, 8).toString() + "'"); + Log.e("TEST", "LENGTH?" + (s.length() == 24)); + Log.e("TEST", "SUB?" + (s.toString().startsWith("briar://"))); + Log.e("TEST", "IS BRIAR LINK?" + + (s.length() == 24 && s.toString().startsWith("briar://"))); + return s.length() == 24 && s.toString().startsWith("briar://"); + } + + private void updateAddButtonState() { + addButton.setEnabled(isBriarLink(linkInput.getText()) && + contactNameInput.getText().length() > 0); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputActivity.java new file mode 100644 index 000000000..e374b2213 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactLinkOutputActivity.java @@ -0,0 +1,73 @@ +package org.briarproject.briar.android.contact; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.view.MenuItem; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.activity.BriarActivity; + +import javax.annotation.Nullable; + +import static android.widget.Toast.LENGTH_SHORT; +import static org.briarproject.bramble.util.StringUtils.getRandomString; + +public class ContactLinkOutputActivity extends BriarActivity { + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + + setContentView(R.layout.activity_contact_link_ouput); + + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setDisplayHomeAsUpEnabled(true); + } + setTitle(R.string.add_contact_via_link_title); + + String link = "briar://" + getRandomString(16); + + TextView linkView = findViewById(R.id.linkView); + linkView.setText(link); + + ClipboardManager clipboard = (ClipboardManager) + getSystemService(CLIPBOARD_SERVICE); + if (clipboard == null) throw new AssertionError(); + ClipData clip = ClipData.newPlainText("Briar link", link); + + Button button = findViewById(R.id.button); + button.setOnClickListener(v -> { + clipboard.setPrimaryClip(clip); + Toast.makeText(this, "Link copied!", LENGTH_SHORT).show(); + }); + + Button enterLinkButton = findViewById(R.id.enterLinkButton); + enterLinkButton.setOnClickListener(v -> startActivity( + new Intent(this, ContactLinkInputActivity.class))); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java index bbf1a3fd2..04ad09662 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ContactListFragment.java @@ -2,8 +2,10 @@ package org.briarproject.briar.android.contact; import android.content.Intent; import android.os.Bundle; +import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.content.ContextCompat; import android.support.v4.util.Pair; import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; @@ -13,6 +15,9 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import com.leinardi.android.speeddial.SpeedDialActionItem; +import com.leinardi.android.speeddial.SpeedDialView; + import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; @@ -49,6 +54,7 @@ import javax.annotation.Nullable; import javax.inject.Inject; import static android.os.Build.VERSION.SDK_INT; +import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE; import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static android.support.v4.view.ViewCompat.getTransitionName; import static java.util.logging.Level.WARNING; @@ -59,7 +65,8 @@ import static org.briarproject.briar.android.conversation.ConversationActivity.C @MethodsNotNullByDefault @ParametersNotNullByDefault -public class ContactListFragment extends BaseFragment implements EventListener { +public class ContactListFragment extends BaseFragment implements EventListener, + SpeedDialView.OnActionSelectedListener { public static final String TAG = ContactListFragment.class.getName(); private static final Logger LOG = Logger.getLogger(TAG); @@ -73,6 +80,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { private ContactListAdapter adapter; private BriarRecyclerView list; + private Snackbar snackbar; // Fields that are accessed from background threads must be volatile @Inject @@ -105,7 +113,11 @@ public class ContactListFragment extends BaseFragment implements EventListener { getActivity().setTitle(R.string.contact_list_button); - View contentView = inflater.inflate(R.layout.list, container, false); + View contentView = inflater.inflate(R.layout.fragment_contact_list, container, false); + + SpeedDialView speedDialView = contentView.findViewById(R.id.speedDial); + speedDialView.inflate(R.menu.contact_list_actions); + speedDialView.setOnActionSelectedListener(this); OnContactClickListener onContactClickListener = (view, item) -> { @@ -144,12 +156,21 @@ public class ContactListFragment extends BaseFragment implements EventListener { list.setEmptyText(getString(R.string.no_contacts)); list.setEmptyAction(getString(R.string.no_contacts_action)); + snackbar = + Snackbar.make(contentView, "There are pending contact requests", + LENGTH_INDEFINITE); + snackbar.getView().setBackgroundResource(R.color.briar_primary); + snackbar.setAction(R.string.show, v -> startActivity( + new Intent(getContext(), ContactLinkInputActivity.class))); + snackbar.setActionTextColor(ContextCompat + .getColor(getContext(), R.color.briar_button_text_positive)); + return contentView; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.contact_list_actions, menu); +// inflater.inflate(R.menu.contact_list_actions, menu); super.onCreateOptionsMenu(menu, inflater); } @@ -167,6 +188,23 @@ public class ContactListFragment extends BaseFragment implements EventListener { } } + @Override + public boolean onActionSelected(SpeedDialActionItem item) { + switch (item.getId()) { + case R.id.action_add_contact: + Intent intent = + new Intent(getContext(), ContactExchangeActivity.class); + startActivity(intent); + return true; + case R.id.action_add_contact_via_link: + startActivity(new Intent(getContext(), + ContactLinkOutputActivity.class)); + return true; + default: + return false; + } + } + @Override public void onStart() { super.onStart(); @@ -175,6 +213,8 @@ public class ContactListFragment extends BaseFragment implements EventListener { notificationManager.clearAllIntroductionNotifications(); loadContacts(); list.startPeriodicUpdate(); + + snackbar.show(); } @Override diff --git a/briar-android/src/main/res/drawable/ic_link.xml b/briar-android/src/main/res/drawable/ic_link.xml new file mode 100644 index 000000000..8b4d9a8b8 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/briar-android/src/main/res/layout/activity_contact_link_input.xml b/briar-android/src/main/res/layout/activity_contact_link_input.xml new file mode 100644 index 000000000..cfd2232ba --- /dev/null +++ b/briar-android/src/main/res/layout/activity_contact_link_input.xml @@ -0,0 +1,67 @@ + + + + + +