[android] rough sketch of UI for adding contacts remotely

This commit is contained in:
Torsten Grote
2018-09-24 18:33:15 -03:00
parent df5ac59fc9
commit 58ffc6e761
37 changed files with 1818 additions and 40 deletions

View File

@@ -425,5 +425,28 @@
android:launchMode="singleTask"
android:theme="@style/BriarTheme.NoActionBar"/>
<activity
android:name=".android.contact.add.remote.AddContactActivity"
android:theme="@style/BriarTheme"
android:label="@string/add_contact_remotely_title_case"
android:windowSoftInputMode="stateHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="briar"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
<activity
android:name=".android.contact.add.remote.PendingRequestsActivity"
android:label="@string/pending_contact_requests"
android:theme="@style/BriarTheme"/>
</application>
</manifest>

View File

@@ -15,8 +15,12 @@ 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.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingRequestsActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ImageActivity;
@@ -168,6 +172,10 @@ public interface ActivityComponent {
void inject(UnlockActivity activity);
void inject(AddContactActivity activity);
void inject(PendingRequestsActivity activity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -191,6 +199,10 @@ public interface ActivityComponent {
void inject(KeyAgreementFragment fragment);
void inject(LinkExchangeFragment fragment);
void inject(NicknameFragment fragment);
void inject(ContactChooserFragment fragment);
void inject(ShareForumFragment fragment);

View File

@@ -3,22 +3,24 @@ package org.briarproject.briar.android.contact;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.UiThread;
import android.support.design.widget.FloatingActionButton;
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;
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 org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
@@ -32,6 +34,8 @@ import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.PendingRequestsActivity;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
@@ -49,11 +53,16 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
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.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -62,7 +71,8 @@ import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ContactListFragment extends BaseFragment implements EventListener {
public class ContactListFragment extends BaseFragment implements EventListener,
OnMenuItemClickListener {
public static final String TAG = ContactListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@@ -76,6 +86,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
@@ -107,7 +118,12 @@ public class ContactListFragment extends BaseFragment implements EventListener {
@Nullable Bundle savedInstanceState) {
requireNonNull(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);
FabSpeedDial speedDialView = contentView.findViewById(R.id.speedDial);
speedDialView.addOnMenuItemClickListener(this);
OnContactClickListener<ContactListItem> onContactClickListener =
(view, item) -> {
@@ -146,26 +162,30 @@ public class ContactListFragment extends BaseFragment implements EventListener {
list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action));
// TODO UiUtils helper method?
snackbar = Snackbar.make(contentView,
R.string.pending_contact_requests_snackbar, LENGTH_INDEFINITE);
snackbar.getView().setBackgroundResource(R.color.briar_primary);
snackbar.setAction(R.string.show, v -> startActivity(
new Intent(getContext(), PendingRequestsActivity.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);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_add_contact:
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
int itemId) {
switch (itemId) {
case R.id.action_add_contact_nearby:
Intent intent =
new Intent(getContext(), ContactExchangeActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
return;
case R.id.action_add_contact_remotely:
startActivity(
new Intent(getContext(), AddContactActivity.class));
}
}
@@ -176,9 +196,24 @@ public class ContactListFragment extends BaseFragment implements EventListener {
notificationManager.clearAllContactNotifications();
notificationManager.clearAllIntroductionNotifications();
loadContacts();
checkForPendingContacts();
list.startPeriodicUpdate();
}
private void checkForPendingContacts() {
listener.runOnDbThread(() -> {
try {
if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(() -> snackbar.dismiss());
} else {
runOnUiThreadUnlessDestroyed(() -> snackbar.show());
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override
public void onStop() {
super.onStop();
@@ -232,6 +267,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
checkForPendingContacts();
} else if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId(), true);
} else if (e instanceof ContactDisconnectedEvent) {
@@ -245,6 +281,13 @@ public class ContactListFragment extends BaseFragment implements EventListener {
(ConversationMessageReceivedEvent) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactStateChangedEvent) {
PendingContactStateChangedEvent pe =
(PendingContactStateChangedEvent) e;
// only re-check pending contacts for initial state
if (pe.getPendingContactState() == WAITING_FOR_CONNECTION) {
checkForPendingContacts();
}
}
}

View File

@@ -0,0 +1,99 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.ACTION_VIEW;
import static android.content.Intent.EXTRA_TEXT;
import static android.widget.Toast.LENGTH_LONG;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class AddContactActivity extends BriarActivity implements
BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_fragment_container);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
AddContactViewModel viewModel =
ViewModelProviders.of(this, viewModelFactory)
.get(AddContactViewModel.class);
viewModel.getRemoteLinkEntered().observe(this, entered -> {
if (entered != null && entered) {
NicknameFragment f = new NicknameFragment();
showNextFragment(f);
}
});
Intent i = getIntent();
if (i != null) {
String action = i.getAction();
if (ACTION_SEND.equals(action) || ACTION_VIEW.equals(action)) {
String text = i.getStringExtra(EXTRA_TEXT);
if (text != null) {
if (viewModel.isValidRemoteContactLink(text)) {
viewModel.setRemoteContactLink(text);
} else {
Toast.makeText(this, R.string.invalid_link, LENGTH_LONG)
.show();
}
}
String uri = i.getDataString();
if (uri != null) {
if (viewModel.isValidRemoteContactLink(uri)) {
viewModel.setRemoteContactLink(uri);
} else {
Toast.makeText(this, R.string.invalid_link, LENGTH_LONG)
.show();
}
}
}
}
if (state == null) {
showInitialFragment(new LinkExchangeFragment());
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,89 @@
package org.briarproject.briar.android.contact.add.remote;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
@NotNullByDefault
public class AddContactViewModel extends AndroidViewModel {
private static Logger LOG = getLogger(AddContactViewModel.class.getName());
private final ContactManager contactManager;
@DatabaseExecutor
private final Executor dbExecutor;
private final MutableLiveData<String> ourLink = new MutableLiveData<>();
private final MutableLiveData<Boolean> remoteLinkEntered =
new MutableLiveData<>();
@Nullable
private String remoteContactLink;
@Inject
public AddContactViewModel(@NonNull Application application,
ContactManager contactManager,
@DatabaseExecutor Executor dbExecutor) {
super(application);
this.contactManager = contactManager;
this.dbExecutor = dbExecutor;
loadOurLink();
}
private void loadOurLink() {
dbExecutor.execute(() -> {
try {
ourLink.postValue(contactManager.getRemoteContactLink());
} catch (DbException e) {
throw new AssertionError(e);
}
});
}
LiveData<String> getOurLink() {
return ourLink;
}
void setRemoteContactLink(String link) {
remoteContactLink = link;
}
@Nullable
String getRemoteContactLink() {
return remoteContactLink;
}
boolean isValidRemoteContactLink(@Nullable CharSequence link) {
return link != null &&
contactManager.isValidRemoteContactLink(link.toString());
}
LiveData<Boolean> getRemoteLinkEntered() {
return remoteLinkEntered;
}
void onRemoteLinkEntered() {
if (remoteContactLink == null) throw new IllegalStateException();
remoteLinkEntered.setValue(true);
}
void addContact(String nickname) {
if (remoteContactLink == null) throw new IllegalStateException();
contactManager.addRemoteContactRequest(remoteContactLink, nickname);
}
}

View File

@@ -0,0 +1,168 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.os.Bundle;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.ShareCompat.IntentBuilder;
import android.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import java.util.regex.Matcher;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Context.CLIPBOARD_SERVICE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.contact.ContactManager.LINK_REGEX;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class LinkExchangeFragment extends BaseFragment {
private static final String TAG = LinkExchangeFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddContactViewModel viewModel;
private ClipboardManager clipboard;
private TextInputLayout linkInputLayout;
private TextInputEditText linkInput;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
View v = inflater.inflate(R.layout.fragment_link_exchange,
container, false);
linkInputLayout = v.findViewById(R.id.linkInputLayout);
linkInput = v.findViewById(R.id.linkInput);
if (viewModel.getRemoteContactLink() != null) {
linkInput.setText(viewModel.getRemoteContactLink());
}
clipboard = (ClipboardManager) requireNonNull(
getContext().getSystemService(CLIPBOARD_SERVICE));
Button pasteButton = v.findViewById(R.id.pasteButton);
pasteButton.setOnClickListener(view -> {
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null)
linkInput.setText(clipData.getItemAt(0).getText());
});
observeOnce(viewModel.getOurLink(), this, this::onOwnLinkLoaded);
return v;
}
private void onOwnLinkLoaded(String link) {
View v = requireNonNull(getView());
TextView linkView = v.findViewById(R.id.linkView);
linkView.setText(link);
Button copyButton = v.findViewById(R.id.copyButton);
ClipData clip = ClipData.newPlainText(
getString(R.string.link_clip_label), link);
copyButton.setOnClickListener(view -> {
clipboard.setPrimaryClip(clip);
Toast.makeText(getContext(), R.string.link_copied_toast,
LENGTH_SHORT).show();
});
copyButton.setEnabled(true);
Button shareButton = v.findViewById(R.id.shareButton);
shareButton.setOnClickListener(view ->
IntentBuilder.from(requireNonNull(getActivity()))
.setText(link)
.setType("text/plain")
.startChooser());
shareButton.setEnabled(true);
Button continueButton = v.findViewById(R.id.addButton);
continueButton.setOnClickListener(view -> onContinueButtonClicked());
continueButton.setEnabled(true);
}
private boolean isInputError() {
Editable linkText = linkInput.getText();
boolean briarLink = viewModel.isValidRemoteContactLink(linkText);
if (!briarLink) {
if (linkText == null || linkText.length() == 0) {
linkInputLayout.setError(getString(R.string.missing_link));
} else {
linkInputLayout.setError(getString(R.string.invalid_link));
}
linkInput.requestFocus();
return true;
} else linkInputLayout.setError(null);
String link = getLink();
boolean isOurLink = link != null &&
("briar://" + link).equals(viewModel.getOurLink().getValue());
if (isOurLink) {
linkInputLayout.setError(getString(R.string.own_link_error));
linkInput.requestFocus();
return true;
} else linkInputLayout.setError(null);
return false;
}
@Nullable
private String getLink() {
CharSequence link = linkInput.getText();
if (link == null) return null;
Matcher matcher = LINK_REGEX.matcher(link);
if (matcher.matches()) // needs to be called before groups become available
return matcher.group(2);
else
return null;
}
private void onContinueButtonClicked() {
if (isInputError()) return;
String linkText = getLink();
if (linkText == null) throw new AssertionError();
viewModel.setRemoteContactLink(linkText);
viewModel.onRemoteLinkEntered();
}
}

View File

@@ -0,0 +1,98 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.MainThread;
import android.support.annotation.UiThread;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class NicknameFragment extends BaseFragment {
private static final String TAG = NicknameFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddContactViewModel viewModel;
private TextInputLayout contactNameLayout;
private TextInputEditText contactNameInput;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
View v = inflater.inflate(R.layout.fragment_nickname,
container, false);
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
Button addButton = v.findViewById(R.id.addButton);
addButton.setOnClickListener(view -> onAddButtonClicked());
contactNameLayout = v.findViewById(R.id.contactNameLayout);
contactNameInput = v.findViewById(R.id.contactNameInput);
return v;
}
@MainThread
@UiThread
private boolean isInputError() {
boolean validContactName = contactNameInput.getText() != null &&
contactNameInput.getText().toString().trim().length() > 0;
if (!validContactName) {
contactNameLayout.setError(getString(R.string.nickname_missing));
contactNameInput.requestFocus();
return true;
} else contactNameLayout.setError(null);
return false;
}
private void onAddButtonClicked() {
if (isInputError()) return;
String name = requireNonNull(contactNameInput.getText()).toString();
viewModel.addContact(name);
Intent intent =
new Intent(getActivity(), PendingRequestsActivity.class);
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,125 @@
package org.briarproject.briar.android.contact.add.remote;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.view.BriarRecyclerView;
import java.util.Collection;
import javax.annotation.Nullable;
import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class PendingRequestsActivity extends BriarActivity
implements EventListener {
@Inject
ContactManager contactManager;
@Inject
EventBus eventBus;
private PendingRequestsAdapter adapter;
private BriarRecyclerView list;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.list);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
adapter = new PendingRequestsAdapter(this, PendingContact.class);
list = findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
}
@Override
public void onStart() {
super.onStart();
eventBus.addListener(this);
list.startPeriodicUpdate();
runOnDbThread(() -> {
try {
Collection<PendingContact> contacts =
contactManager.getPendingContacts();
addPendingContacts(contacts);
} catch (DbException e) {
e.printStackTrace();
}
});
}
@Override
protected void onStop() {
super.onStop();
list.stopPeriodicUpdate();
adapter.clear();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
runOnDbThread(() -> {
try {
Contact contact = contactManager
.getContact(((ContactAddedEvent) e).getContactId());
runOnUiThreadUnlessDestroyed(() -> {
adapter.remove(contact);
if (adapter.isEmpty()) finish();
});
} catch (DbException e1) {
e1.printStackTrace();
}
});
}
}
private void addPendingContacts(Collection<PendingContact> contacts) {
runOnUiThreadUnlessDestroyed(() -> {
if (contacts.isEmpty()) {
list.showData();
} else {
adapter.addAll(contacts);
}
});
}
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.briar.android.contact.add.remote;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
@NotNullByDefault
public class PendingRequestsAdapter extends
BriarAdapter<PendingContact, PendingRequestsViewHolder> {
public PendingRequestsAdapter(Context ctx, Class<PendingContact> c) {
super(ctx, c);
}
@Override
public PendingRequestsViewHolder onCreateViewHolder(
ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_pending_contact, viewGroup, false);
return new PendingRequestsViewHolder(v);
}
@Override
public void onBindViewHolder(
PendingRequestsViewHolder pendingRequestsViewHolder, int i) {
pendingRequestsViewHolder.bind(items.get(i));
}
@Override
public int compare(PendingContact item1, PendingContact item2) {
return (int) (item1.getTimestamp() - item2.getTimestamp());
}
@Override
public boolean areContentsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp();
}
@Override
public boolean areItemsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp();
}
// TODO use PendingContactId
public void remove(Contact contact) {
for (int i = 0; i < items.size(); i++) {
if (items.get(i).getAlias().equals(contact.getAuthor().getName())) {
items.removeItemAt(i);
return;
}
}
}
}

View File

@@ -0,0 +1,64 @@
package org.briarproject.briar.android.contact.add.remote;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.TextAvatarView;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
@NotNullByDefault
public class PendingRequestsViewHolder extends ViewHolder {
private final TextAvatarView avatar;
private final TextView name;
private final TextView time;
private final TextView status;
public PendingRequestsViewHolder(View v) {
super(v);
avatar = v.findViewById(R.id.avatar);
name = v.findViewById(R.id.name);
time = v.findViewById(R.id.time);
status = v.findViewById(R.id.status);
}
public void bind(PendingContact item) {
avatar.setText(item.getAlias());
avatar.setBackgroundBytes(toUtf8(item.getAlias() + item.getTimestamp()));
name.setText(item.getAlias());
time.setText(formatDate(time.getContext(), item.getTimestamp()));
int color = ContextCompat
.getColor(status.getContext(), R.color.briar_green);
switch (item.getState()) {
case WAITING_FOR_CONNECTION:
color = ContextCompat
.getColor(status.getContext(), R.color.briar_yellow);
status.setText(R.string.waiting_for_contact_to_come_online);
break;
case CONNECTED:
status.setText(R.string.connecting);
break;
case ADDING_CONTACT:
status.setText(R.string.adding_contact);
break;
case FAILED:
// TODO add remove button
color = ContextCompat
.getColor(status.getContext(), R.color.briar_red);
status.setText(R.string.adding_contact_failed);
break;
default:
throw new IllegalStateException();
}
status.setTextColor(color);
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.viewmodel;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import org.briarproject.briar.android.contact.add.remote.AddContactViewModel;
import org.briarproject.briar.android.conversation.ConversationViewModel;
import org.briarproject.briar.android.conversation.ImageViewModel;
@@ -27,6 +28,12 @@ public abstract class ViewModelModule {
abstract ViewModel bindImageViewModel(
ImageViewModel imageViewModel);
@Binds
@IntoMap
@ViewModelKey(AddContactViewModel.class)
abstract ViewModel bindAddContactViewModel(
AddContactViewModel addContactViewModel);
@Binds
@Singleton
abstract ViewModelProvider.Factory bindViewModelFactory(

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="32dp"/>
<solid
android:color="@color/briar_accent"/>
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="32dp"/>
<solid
android:color="@color/briar_green"/>
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M9,5v2h6.59L4,18.59 5.41,20 17,8.41V15h2V5z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,5.41L18.59,4 7,15.59V9H5v10h10v-2H8.41z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#2A93C6"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#2A93C6"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,2h-4.18C14.4,0.84 13.3,0 12,0c-1.3,0 -2.4,0.84 -2.82,2L5,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM12,2c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM19,20L5,20L5,4h2v3h10L17,4h2v16z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFF"
android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11.1731,2.4031C9.9686,2.4471 8.7753,2.713 7.6641,3.2033 8.187,3.7263 8.3967,4.1446 8.6059,4.563 11.6391,3.4125 15.0914,4.0394 17.4971,6.445c0.7322,0.7321 1.3593,1.5692 1.6731,2.5105 0.4184,-0.2092 0.9412,-0.3134 1.4642,-0.3134h0.1045C20.2158,7.387 19.483,6.3401 18.5416,5.2942 16.5283,3.2808 13.823,2.3062 11.1731,2.4031ZM5.1695,3.2316A2.7194,2.7194 0,0 0,2.4501 5.951,2.7194 2.7194,0 0,0 5.1695,8.6704 2.7194,2.7194 0,0 0,7.8889 5.951,2.7194 2.7194,0 0,0 5.1695,3.2316ZM2.5404,8.5359C0.9715,12.1966 1.7027,16.4848 4.6313,19.4134c1.8827,1.9872 4.4972,2.9301 7.0074,2.9301 2.5102,0 5.1264,-0.9428 7.0092,-2.9301 0.9413,-0.9414 1.6724,-2.091 2.1953,-3.3461h-0.1045c-0.5229,0 -1.0458,-0.1042 -1.4642,-0.3134 -0.4184,0.9414 -0.9409,1.7783 -1.6731,2.5105 -3.2423,3.2424 -8.5771,3.2424 -11.8194,0C3.2719,15.8588 2.6451,12.4064 3.7957,9.3733 3.3773,9.1641 2.9588,8.9542 2.5404,8.5359ZM11.5944,9.3804a2.9913,2.9913 0,0 0,-2.9903 2.9903,2.9913 2.9913,0 0,0 2.9903,2.992 2.9913,2.9913 0,0 0,2.992 -2.992,2.9913 2.9913,0 0,0 -2.992,-2.9903zM20.7334,9.8035a2.7194,2.7194 0,0 0,-2.7194 2.7194,2.7194 2.7194,0 0,0 2.7194,2.7194 2.7194,2.7194 0,0 0,2.7194 -2.7194,2.7194 2.7194,0 0,0 -2.7194,-2.7194z"/>
</vector>

View File

@@ -0,0 +1,57 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="237dp"
android:viewportHeight="178"
android:viewportWidth="150">
<path
android:fillAlpha="0.25"
android:pathData="M85.19,0.128 L3.357,0.329C2.465,0.331 1.611,0.69 0.981,1.329 0.352,1.967 -0.001,2.832 0,3.733L0.373,156.201c0.001,0.446 0.089,0.888 0.26,1.3 0.17,0.412 0.419,0.786 0.732,1.101 0.313,0.315 0.685,0.564 1.093,0.734 0.409,0.17 0.846,0.257 1.288,0.256l81.834,-0.201c0.892,-0.003 1.746,-0.363 2.375,-1.002 0.629,-0.639 0.981,-1.504 0.98,-2.405L88.554,3.516C88.551,2.617 88.196,1.755 87.565,1.12 86.935,0.485 86.081,0.128 85.19,0.128Z">
</path>
<path
android:fillAlpha="0.175"
android:fillColor="#6d6d6d"
android:pathData="M84.282,-0.002 L4.276,0.196C2.574,0.2 1.198,1.598 1.202,3.318L1.567,154.298c0.004,1.72 1.387,3.111 3.089,3.107l80.006,-0.198c1.702,-0.004 3.078,-1.402 3.074,-3.122L87.37,3.105C87.366,1.385 85.983,-0.006 84.282,-0.002Z"/>
<path
android:fillAlpha="0.25"
android:fillColor="#646464"
android:pathData="M81.939,3.957L65.934,3.996C65.689,5.664 64.862,7.188 63.602,8.293C62.342,9.398 60.732,10.01 59.064,10.02L29.303,10.092C27.635,10.091 26.021,9.487 24.756,8.389C23.49,7.29 22.655,5.77 22.402,4.104L6.637,4.143C5.769,4.145 4.938,4.495 4.326,5.117C3.714,5.739 3.371,6.58 3.373,7.457L3.461,44.068L3.717,150.168C3.721,151.045 4.069,151.885 4.686,152.502C5.302,153.119 6.136,153.464 7.004,153.461L82.307,153.275C83.172,153.27 83.999,152.921 84.609,152.301C85.219,151.681 85.561,150.841 85.561,149.967L85.307,43.563L85.221,7.256C85.218,6.379 84.871,5.539 84.256,4.92C83.641,4.301 82.807,3.955 81.939,3.957zM52.652,5.791L34.357,6.068C34.108,6.072 33.908,6.279 33.912,6.531L33.916,6.83C33.92,7.082 34.125,7.285 34.375,7.281L52.672,7.002C52.921,6.998 53.119,6.791 53.115,6.539L53.111,6.24C53.108,5.988 52.902,5.787 52.652,5.791z"/>
<path
android:fillAlpha="0.25"
android:fillColor="#dbdbdb"
android:pathData="M57.204,6.934C57.602,6.928 57.919,6.597 57.913,6.195 57.907,5.793 57.58,5.471 57.182,5.477c-0.398,0.006 -0.716,0.337 -0.71,0.739 0.006,0.402 0.333,0.723 0.731,0.717z"/>
<path
android:fillAlpha="0.25"
android:fillColor="#e0e0e0"
android:pathData="m17.641,19.459c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.446 7.576,-7.695 0,-4.25 -3.392,-7.695 -7.576,-7.695zM37.523,23.637v3.234h40.117v-3.234zM37.523,30.104v3.234L77.641,33.338L77.641,30.104ZM17.641,44.088c-4.185,0 -7.576,3.444 -7.576,7.693 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.446 7.576,-7.695 0,-4.25 -3.392,-7.693 -7.576,-7.693zM37.523,48.266L37.523,51.5h40.117v-3.234zM37.523,54.732v3.232L77.641,57.965L77.641,54.732ZM17.641,68.715c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.446 7.576,-7.695 0,-4.25 -3.392,-7.695 -7.576,-7.695zM37.523,72.895v3.232h40.117v-3.232zM37.523,79.361L37.523,82.594L77.641,82.594L77.641,79.361ZM17.641,93.344c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.695 7.576,7.695 4.185,0 7.576,-3.445 7.576,-7.695 0,-4.25 -3.392,-7.695 -7.576,-7.695zM37.523,97.523v3.232h40.117v-3.232zM37.523,103.988v3.234h40.117v-3.234zM17.641,117.973c-4.185,0 -7.576,3.446 -7.576,7.695 0,4.25 3.392,7.693 7.576,7.693 4.185,0 7.576,-3.443 7.576,-7.693 0,-4.249 -3.392,-7.695 -7.576,-7.695zM37.523,122.15v3.234h40.117v-3.234zM37.523,128.617v3.234h40.117v-3.234z"/>
<path
android:fillColor="#313131"
android:pathData="m136.95,18.407 l-80.006,0.198c-1.702,0.004 -3.078,1.402 -3.074,3.122l0.365,150.981c0.004,1.72 1.387,3.111 3.089,3.107l80.006,-0.198c1.702,-0.004 3.078,-1.402 3.074,-3.122L140.039,21.514c-0.004,-1.72 -1.387,-3.111 -3.089,-3.107z"/>
<path
android:fillColor="#3e3e3e"
android:pathData="M134.609,22.357L118.6,22.395C118.356,24.062 117.528,25.587 116.268,26.691C115.008,27.796 113.398,28.408 111.73,28.418L81.969,28.492C80.301,28.491 78.689,27.886 77.424,26.787C76.158,25.689 75.323,24.168 75.07,22.502L59.305,22.541C58.437,22.543 57.604,22.894 56.992,23.516C56.38,24.137 56.037,24.98 56.039,25.857L56.129,62.469L56.748,62.467L56.129,62.471L56.383,168.57C56.385,169.447 56.733,170.287 57.348,170.906C57.963,171.524 58.796,171.871 59.664,171.869L134.967,171.684C135.835,171.682 136.665,171.331 137.277,170.709C137.889,170.088 138.232,169.246 138.23,168.369L137.973,62.219L137.975,62.219L137.889,25.656C137.887,24.779 137.541,23.939 136.926,23.32C136.311,22.702 135.477,22.356 134.609,22.357z"/>
<path
android:fillColor="#e0e0e0"
android:pathData="M109.85,23.891C109.452,23.897 109.135,24.229 109.141,24.631C109.147,25.033 109.473,25.354 109.871,25.348C110.269,25.342 110.586,25.01 110.58,24.607C110.574,24.205 110.248,23.885 109.85,23.891zM105.32,24.201L87.025,24.479C86.776,24.482 86.576,24.691 86.58,24.943L86.584,25.242C86.588,25.494 86.793,25.695 87.043,25.691L105.34,25.414C105.589,25.41 105.787,25.203 105.783,24.951L105.779,24.65C105.775,24.398 105.569,24.197 105.32,24.201zM63.365,37.568L63.365,53.738L79.365,53.738L79.365,37.568L63.365,37.568zM86.748,37.568L86.748,40.801L126.865,40.801L126.865,37.568L86.748,37.568zM86.748,44.037L86.748,47.27L126.865,47.27L126.865,44.037L86.748,44.037zM71.787,112.996C67.603,112.996 64.211,116.441 64.211,120.691C64.211,124.941 67.603,128.387 71.787,128.387C75.972,128.387 79.365,124.941 79.365,120.691C79.365,116.441 75.972,112.996 71.787,112.996zM86.748,116.928L86.748,120.16L126.865,120.16L126.865,116.928L86.748,116.928zM86.748,123.393L86.748,126.627L126.865,126.627L126.865,123.393L86.748,123.393zM71.787,142.602C67.603,142.602 64.211,146.047 64.211,150.297C64.211,154.547 67.603,157.99 71.787,157.99C75.972,157.99 79.365,154.547 79.365,150.297C79.365,146.047 75.972,142.602 71.787,142.602zM86.748,145.535L86.748,148.768L126.865,148.768L126.865,145.535L86.748,145.535zM86.748,152.002L86.748,155.234L126.865,155.234L126.865,152.002L86.748,152.002z"/>
<path
android:fillColor="#d5d6d7"
android:pathData="M148.744,68.665H45.619v33.583h103.125z"/>
<path
android:fillColor="#87c214"
android:pathData="m97.822,50.504v5.723h17.967v-5.723zM86.748,81.104v3.232h40.117v-3.232zM86.748,87.572v3.232h40.117v-3.232z"/>
<path
android:fillColor="#f5f5f5"
android:pathData="m66.865,92.314c4.185,0 7.577,-3.445 7.577,-7.695 0,-4.25 -3.392,-7.695 -7.577,-7.695 -4.185,0 -7.577,3.445 -7.577,7.695 0,4.25 3.392,7.695 7.577,7.695z"/>
<path
android:fillColor="#87c214"
android:pathData="m70.13,90.006h-6.574c-0.315,0.002 -0.625,0.082 -0.904,0.231 -0.278,0.149 -0.517,0.365 -0.696,0.627 1.477,0.856 3.147,1.311 4.85,1.321 1.702,0.01 3.378,-0.427 4.864,-1.266 -0.167,-0.265 -0.393,-0.486 -0.661,-0.645 -0.268,-0.159 -0.569,-0.251 -0.879,-0.269z"/>
<path
android:fillColor="#333333"
android:pathData="m66.861,80.746c-2.08,0 -3.766,1.704 -3.766,3.807 0,0.394 0.076,0.766 0.186,1.123 -0.328,0.813 -0.822,1.878 -1.158,1.709 0,0 5.07,4.425 9.545,0 -0.355,-0.613 -0.773,-1.184 -1.219,-1.732 0.105,-0.35 0.178,-0.715 0.178,-1.1 0,-2.102 -1.686,-3.807 -3.766,-3.807z"/>
<path
android:fillColor="#fda57d"
android:pathData="m66.861,81.57c-1.801,0 -3.302,1.279 -3.674,2.986 -0.005,-0 -0.009,-0.006 -0.014,-0.006 -0.193,0 -0.35,0.297 -0.35,0.664 0,0.347 0.142,0.623 0.32,0.652 0.181,1.408 1.109,2.569 2.381,3.059v0.732c0,0.323 0.127,0.635 0.354,0.863 0.226,0.229 0.534,0.357 0.854,0.357h0.225c0.159,0 0.316,-0.032 0.463,-0.094 0.147,-0.061 0.28,-0.15 0.393,-0.264 0.112,-0.113 0.201,-0.248 0.262,-0.396 0.061,-0.148 0.092,-0.308 0.092,-0.469v-0.721c1.289,-0.484 2.232,-1.655 2.412,-3.076 0.168,-0.049 0.299,-0.312 0.299,-0.645 0,-0.363 -0.153,-0.656 -0.344,-0.662 -0.373,-1.705 -1.873,-2.982 -3.672,-2.982z"/>
<path
android:fillColor="#333333"
android:pathData="m63.253,83.934h7.179c0,0 -0.612,-2.93 -3.328,-2.74 -2.715,0.19 -3.852,2.74 -3.852,2.74z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#2A93C6"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.briarproject.briar.android.view.BriarRecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:scrollToEnd="false"/>
<io.github.kobakei.materialfabspeeddial.FabSpeedDial
android:id="@+id/speedDial"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fab_fabDrawable="@drawable/ic_action_add"
app:fab_fabRippleColor="@android:color/transparent"
app:fab_menu="@menu/contact_list_actions"
app:fab_miniFabTextBackground="@color/briar_accent"
app:fab_miniFabTextColor="@android:color/white"/>
</android.support.design.widget.CoordinatorLayout>

View File

@@ -0,0 +1,233 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_large">
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5"/>
<TextView
android:id="@+id/stepOne"
style="@style/StepBubble"
android:text="@string/step_1"
app:layout_constraintBottom_toTopOf="@+id/stepOneText"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"/>
<TextView
android:id="@+id/stepOneText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/send_link_title"
app:layout_constraintBottom_toTopOf="@+id/yourLinkIcon"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stepOne"/>
<View
android:id="@+id/stepConnector"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_margin="16dp"
android:alpha="0.5"
android:background="@color/briar_accent"
app:layout_constraintBottom_toBottomOf="@+id/stepOne"
app:layout_constraintEnd_toStartOf="@+id/stepTwo"
app:layout_constraintStart_toEndOf="@+id/stepOne"
app:layout_constraintTop_toTopOf="@+id/stepOne"/>
<TextView
android:id="@+id/stepTwo"
style="@style/StepBubble.Upcoming"
android:text="@string/step_2"
app:layout_constraintBottom_toTopOf="@+id/stepTwoText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="@+id/stepOne"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed"/>
<TextView
android:id="@+id/stepTwoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:alpha="0.5"
android:text="@string/add_contact_choose_nickname"
app:layout_constraintBottom_toTopOf="@+id/yourLinkIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/stepTwo"/>
<android.support.v7.widget.AppCompatImageView
android:id="@+id/yourLinkIcon"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginTop="32dp"
android:background="@drawable/bubble_accent"
android:scaleType="center"
android:src="@drawable/ic_call_made"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stepOneText"
app:tint="@color/briar_white"/>
<TextView
android:id="@+id/yourLink"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:gravity="left|start"
android:text="@string/your_link"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/yourLinkIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/yourLinkIcon"
app:layout_constraintTop_toTopOf="@+id/yourLinkIcon"/>
<TextView
android:id="@+id/linkView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/briar_white"
android:ellipsize="end"
android:padding="8dp"
android:singleLine="true"
android:textColor="@color/briar_primary"
android:textIsSelectable="true"
android:textSize="18sp"
android:typeface="monospace"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/yourLinkIcon"
tools:text="briar://scnsdflamslkfjgluoblmksdfbwevlewajfdlkjewwhqliafskfjhskdjhvoieiv"/>
<Button
android:id="@+id/copyButton"
style="@style/BriarButtonFlat.Positive.Tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_content_copy"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_content_copy"
android:enabled="false"
android:text="@string/copy_button"
app:layout_constraintEnd_toStartOf="@id/shareButton"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkView"/>
<Button
android:id="@+id/shareButton"
style="@style/BriarButtonFlat.Positive.Tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/social_share_blue"
android:drawablePadding="8dp"
android:drawableStart="@drawable/social_share_blue"
android:enabled="false"
android:text="@string/share_button"
app:layout_constraintBottom_toBottomOf="@id/copyButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/copyButton"
app:layout_constraintTop_toTopOf="@id/copyButton"/>
<android.support.v7.widget.AppCompatImageView
android:id="@+id/linkInputIcon"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:background="@drawable/bubble_accent"
android:scaleType="center"
android:src="@drawable/ic_call_received"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/copyButton"
app:tint="@color/briar_white"/>
<TextView
android:id="@+id/inputLink"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:gravity="left|start"
android:text="@string/contact_link_intro"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/linkInputIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/linkInputIcon"
app:layout_constraintTop_toTopOf="@+id/linkInputIcon"/>
<android.support.design.widget.TextInputLayout
android:id="@+id/linkInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:errorEnabled="true"
app:hintEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkInputIcon">
<android.support.design.widget.TextInputEditText
android:id="@+id/linkInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/contact_link_hint"
android:importantForAutofill="no"
android:inputType="textUri"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/pasteButton"
style="@style/BriarButtonFlat.Positive.Tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_content_paste"
android:drawablePadding="8dp"
android:drawableStart="@drawable/ic_content_paste"
android:text="@string/paste_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linkInputLayout"
app:layout_constraintVertical_bias="0.0"/>
<Button
android:id="@+id/addButton"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:enabled="false"
android:text="@string/continue_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pasteButton"
app:layout_constraintVertical_bias="1.0"
tools:enabled="true"/>
</android.support.constraint.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_large">
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5"/>
<android.support.v7.widget.AppCompatImageView
android:id="@+id/stepOne"
style="@style/StepBubble.Completed"
app:layout_constraintBottom_toTopOf="@+id/stepOneText"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"/>
<TextView
android:id="@+id/stepOneText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/send_link_title"
app:layout_constraintBottom_toTopOf="@+id/imageView"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stepOne"/>
<View
android:id="@+id/stepConnector"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_margin="16dp"
android:background="@color/briar_accent"
app:layout_constraintBottom_toBottomOf="@+id/stepOne"
app:layout_constraintEnd_toStartOf="@+id/stepTwo"
app:layout_constraintStart_toEndOf="@+id/stepOne"
app:layout_constraintTop_toTopOf="@+id/stepOne"/>
<TextView
android:id="@+id/stepTwo"
style="@style/StepBubble"
android:text="@string/step_2"
app:layout_constraintBottom_toTopOf="@+id/stepTwoText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="@+id/stepOne"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed"/>
<TextView
android:id="@+id/stepTwoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/add_contact_choose_nickname"
app:layout_constraintBottom_toTopOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/stepTwo"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:src="@drawable/ic_nickname"
app:layout_constraintBottom_toTopOf="@+id/nicknameIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stepOneText"
tools:ignore="ContentDescription"/>
<android.support.v7.widget.AppCompatImageView
android:id="@+id/nicknameIcon"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginTop="32dp"
android:background="@drawable/bubble_accent"
android:scaleType="center"
android:src="@drawable/ic_person"
app:layout_constraintBottom_toTopOf="@+id/contactNameLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
app:tint="@color/briar_white"/>
<TextView
android:id="@+id/nicknameIntro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:gravity="left|start"
android:text="@string/nickname_intro"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/nicknameIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/nicknameIcon"
app:layout_constraintTop_toTopOf="@+id/nicknameIcon"/>
<android.support.design.widget.TextInputLayout
android:id="@+id/contactNameLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:errorEnabled="true"
app:hintEnabled="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/nicknameIcon">
<android.support.design.widget.TextInputEditText
android:id="@+id/contactNameInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/add_contact_choose_a_nickname"
android:importantForAutofill="no"
android:inputType="text|textCapWords"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/addButton"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_contact_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout"
app:layout_constraintVertical_bias="1.0"/>
</android.support.constraint.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:id="@+id/linearLayout4"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.briarproject.briar.android.view.TextAvatarView
android:id="@+id/avatar"
android:layout_width="@dimen/listitem_picture_frame_size"
android:layout_height="@dimen/listitem_picture_frame_size"
android:layout_marginBottom="8dp"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toTopOf="@+id/divider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_large"
android:layout_marginStart="@dimen/margin_large"
android:layout_marginTop="@dimen/margin_large"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/status"
app:layout_constraintEnd_toStartOf="@+id/time"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toEndOf="@+id/avatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="This is a name of a contact"/>
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:text="@string/waiting_for_contact_to_come_online"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/divider"
app:layout_constraintEnd_toStartOf="@+id/time"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/name"
app:layout_constraintTop_toBottomOf="@+id/name"/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_large"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/name"
app:layout_constraintTop_toTopOf="parent"
tools:text="Dec 24"/>
<View
android:id="@+id/divider"
style="@style/Divider.ContactList"
android:layout_width="0dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/avatar"/>
</android.support.constraint.ConstraintLayout>

View File

@@ -4,9 +4,17 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_add_contact"
android:icon="@drawable/ic_add_white"
android:title="@string/add_contact_title"
app:showAsAction="ifRoom"/>
android:id="@+id/action_add_contact_nearby"
android:icon="@drawable/ic_nearby"
android:orderInCategory="3"
android:title="@string/add_contact_nearby_title"
app:showAsAction="never"/>
</menu>
<item
android:id="@+id/action_add_contact_remotely"
android:icon="@drawable/ic_link_menu"
android:orderInCategory="2"
android:title="@string/add_contact_remotely_title"
app:showAsAction="never"/>
</menu>

View File

@@ -8,6 +8,7 @@
<color name="briar_blue_elio_light">#3C80A9</color>
<color name="briar_blue_light">#2A93C6</color>
<color name="briar_blue_grey">#EBEFF2</color>
<color name="briar_yellow">#9e9d24</color>
<color name="briar_green">#5C940D</color>
<color name="briar_green_light">#95D220</color>
<color name="briar_red">#ff0000</color>

View File

@@ -150,7 +150,8 @@
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
<!-- Adding Contacts -->
<string name="add_contact_title">Add a Contact</string>
<string name="add_contact_title">Add Contact Nearby</string>
<string name="face_to_face">You must meet up with the person you want to add as a contact.\n\nThis will prevent anyone from impersonating you or reading your messages in future.</string>
<string name="continue_button">Continue</string>
<string name="try_again_button">Try Again</string>
@@ -168,6 +169,47 @@
<string name="connection_error_explanation">Please check that you\'re both connected to the same Wi-Fi network.</string>
<string name="connection_error_feedback">If this problem persists, please <a href="feedback">send feedback</a> to help us improve the app.</string>
<!-- Adding Contacts Remotely -->
<string name="add_contact_remotely_title_case">Add Contact At A Distance</string>
<string name="add_contact_nearby_title">Add contact nearby</string>
<string name="add_contact_remotely_title">Add contact at a distance</string>
<string name="contact_name_hint">Give contact a nickname</string>
<string name="contact_link_intro">Enter the link from your contact here</string>
<string name="contact_link_hint">Contact\'s link</string>
<string name="paste_button">Paste</string>
<string name="scan_qr_code_button">QR Code</string>
<string name="add_contact_button">Add contact</string>
<string name="copy_button">Copy</string>
<string name="share_button">Share</string>
<string name="show_qr_code_button">QR Code</string>
<string name="send_link_title">Exchange links</string>
<string name="add_contact_choose_nickname">Choose Nickname</string>
<string name="add_contact_choose_a_nickname">Enter a nickname</string>
<string name="nickname_intro">Give your contact a nickname! Only you can see it.</string>
<string name="your_link">Give this link to the contact you want to add</string>
<string name="link_clip_label">Briar link</string>
<string name="link_copied_toast">Link copied</string>
<string name="pending_contact_requests_snackbar">"There are pending contact requests"</string>
<string name="pending_contact_requests">Pending contact requests</string>
<string name="add_contact_remote_connecting">Connecting…</string>
<string name="scan_qr_code_title">Scan QR Code</string>
<string name="show_qr_code_title">Your QR Code</string>
<string name="camera_error_toast">Camera Error</string>
<string name="waiting_for_contact_to_come_online">Waiting for contact to come online…</string>
<string name="connecting">Connecting…</string>
<string name="adding_contact">Adding contact…</string>
<string name="adding_contact_failed">Adding contact has failed</string>
<string name="own_link_error">Please enter your contact\'s link</string>
<string name="nickname_missing">Please enter a nickname</string>
<string name="invalid_link">Invalid link</string>
<string name="missing_link">Please enter link</string>
<!-- This is a numeral indicating the first step in a serious of screens -->
<string name="step_1">1</string>
<!-- This is a numeral indicating the second step in a serious of screens -->
<string name="step_2">2</string>
<!-- Introductions -->
<string name="introduction_onboarding_title">Introduce your contacts</string>
<string name="introduction_onboarding_text">You can introduce your contacts to each other, so they don\'t need to meet in person to connect on Briar.</string>

View File

@@ -124,4 +124,23 @@
<item name="android:layout_margin">@dimen/margin_small</item>
</style>
<style name="StepBubble">
<item name="android:layout_width">28dp</item>
<item name="android:layout_height">28dp</item>
<item name="android:background">@drawable/bubble_accent</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@color/briar_white</item>
<item name="android:textSize">18sp</item>
</style>
<style name="StepBubble.Upcoming">
<item name="android:alpha">0.5</item>
</style>
<style name="StepBubble.Completed">
<item name="android:background">@drawable/bubble_completed</item>
<item name="android:scaleType">center</item>
<item name="android:src">@drawable/ic_check_white</item>
</style>
</resources>