Merge branch '41-alias-frontend' into 'master'

Add UI for changing contact aliases

Closes #41

See merge request briar/briar!965
This commit is contained in:
Torsten Grote
2018-11-06 18:09:47 +00:00
16 changed files with 491 additions and 115 deletions

View File

@@ -105,6 +105,7 @@ dependencies {
implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:support-annotations:$supportVersion"
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation('ch.acra:acra:4.11') {
exclude module: 'support-v4'

View File

@@ -1,5 +1,7 @@
package org.briarproject.briar.android;
import android.arch.lifecycle.ViewModelProvider;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule;
@@ -154,6 +156,8 @@ public interface AndroidComponent
CircumventionProvider circumventionProvider();
ViewModelProvider.Factory viewModelFactory();
void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService);

View File

@@ -32,6 +32,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
@@ -57,7 +58,7 @@ import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
@Module
@Module(includes = ViewModelModule.class)
public class AppModule {
static class EagerSingletons {

View File

@@ -15,6 +15,7 @@ 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.AliasDialogFragment;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.contact.ConversationActivity;
@@ -211,4 +212,7 @@ public interface ActivityComponent {
void inject(ScreenFilterDialogFragment fragment);
void inject(ContactExchangeErrorFragment fragment);
void inject(AliasDialogFragment aliasDialogFragment);
}

View File

@@ -0,0 +1,72 @@
package org.briarproject.briar.android.contact;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BriarActivity;
import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
public class AliasDialogFragment extends AppCompatDialogFragment {
final static String TAG = AliasDialogFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private ConversationViewModel viewModel;
private EditText aliasEditText;
public static AliasDialogFragment newInstance() {
return new AliasDialogFragment();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
if (getActivity() == null) return;
((BriarActivity) getActivity()).getActivityComponent().inject(this);
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(ConversationViewModel.class);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_alias_dialog, container,
false);
aliasEditText = v.findViewById(R.id.aliasEditText);
Contact contact = requireNonNull(viewModel.getContact().getValue());
String alias = contact.getAlias();
aliasEditText.setText(alias);
if (alias != null) aliasEditText.setSelection(alias.length());
Button setButton = v.findViewById(R.id.setButton);
setButton.setOnClickListener(v1 -> {
viewModel.setContactAlias(aliasEditText.getText().toString());
getDialog().dismiss();
});
Button cancelButton = v.findViewById(R.id.cancelButton);
cancelButton.setOnClickListener(v1 -> getDialog().cancel());
return v;
}
}

View File

@@ -1,7 +1,8 @@
package org.briarproject.briar.android.contact;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
@@ -23,7 +24,6 @@ import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.FormatException;
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.ContactRemovedEvent;
@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
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.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
@@ -96,6 +95,7 @@ import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptSt
import static android.support.v4.view.ViewCompat.setTransitionName;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
@@ -105,6 +105,7 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRO
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
@@ -131,8 +132,8 @@ public class ConversationActivity extends BriarActivity
Executor cryptoExecutor;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final MutableLiveData<String> contactName = new MutableLiveData<>();
private ConversationViewModel viewModel;
private ConversationVisitor visitor;
private ConversationAdapter adapter;
private Toolbar toolbar;
@@ -163,14 +164,18 @@ public class ConversationActivity extends BriarActivity
volatile BlogSharingManager blogSharingManager;
@Inject
volatile GroupInvitationManager groupInvitationManager;
@Inject
ViewModelProvider.Factory viewModelFactory;
private volatile ContactId contactId;
@Nullable
private volatile AuthorId contactAuthorId;
@Nullable
private volatile GroupId messagingGroupId;
@SuppressWarnings("ConstantConditions")
private final Observer<String> contactNameObserver = name -> {
requireNonNull(name);
loadMessages();
};
@Override
public void onCreate(@Nullable Bundle state) {
setSceneTransitionAnimation();
@@ -181,20 +186,37 @@ public class ConversationActivity extends BriarActivity
if (id == -1) throw new IllegalStateException();
contactId = new ContactId(id);
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class);
viewModel.setContactId(contactId);
setContentView(R.layout.activity_conversation);
// Custom Toolbar
toolbar = setUpCustomToolbar(true);
if (toolbar != null) {
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName);
}
toolbar = requireNonNull(setUpCustomToolbar(true));
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName);
observeOnce(viewModel.getContactAuthorId(), this, authorId -> {
requireNonNull(authorId);
toolbarAvatar.setImageDrawable(
new IdenticonDrawable(authorId.getBytes()));
});
viewModel.getContactDisplayName().observe(this, contactName -> {
requireNonNull(contactName);
toolbarTitle.setText(contactName);
});
viewModel.isContactDeleted().observe(this, deleted -> {
requireNonNull(deleted);
if (deleted) finish();
});
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
visitor = new ConversationVisitor(this, this, contactName);
visitor = new ConversationVisitor(this, this,
viewModel.getContactDisplayName());
adapter = new ConversationAdapter(this, this);
list = findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this));
@@ -229,7 +251,7 @@ public class ConversationActivity extends BriarActivity
notificationManager.blockContactNotification(contactId);
notificationManager.clearContactNotification(contactId);
displayContactOnlineStatus();
loadContactDetailsAndMessages();
viewModel.getContactDisplayName().observe(this, contactNameObserver);
list.startPeriodicUpdate();
}
@@ -238,6 +260,7 @@ public class ConversationActivity extends BriarActivity
super.onStop();
eventBus.removeListener(this);
notificationManager.unblockContactNotification(contactId);
viewModel.getContactDisplayName().removeObserver(contactNameObserver);
list.stopPeriodicUpdate();
}
@@ -249,6 +272,8 @@ public class ConversationActivity extends BriarActivity
enableIntroductionActionIfAvailable(
menu.findItem(R.id.action_introduction));
enableAliasActionIfAvailable(
menu.findItem(R.id.action_set_alias));
return super.onCreateOptionsMenu(menu);
}
@@ -266,6 +291,10 @@ public class ConversationActivity extends BriarActivity
intent.putExtra(CONTACT_ID, contactId.getInt());
startActivityForResult(intent, REQUEST_INTRODUCTION);
return true;
case R.id.action_set_alias:
AliasDialogFragment.newInstance().show(
getSupportFragmentManager(), AliasDialogFragment.TAG);
return true;
case R.id.action_social_remove_person:
askToRemoveContact();
return true;
@@ -274,36 +303,6 @@ public class ConversationActivity extends BriarActivity
}
}
private void loadContactDetailsAndMessages() {
runOnDbThread(() -> {
try {
long start = now();
if (contactAuthorId == null) {
Contact contact = contactManager.getContact(contactId);
contactName.postValue(contact.getAuthor().getName());
contactAuthorId = contact.getAuthor().getId();
}
logDuration(LOG, "Loading contact", start);
loadMessages();
displayContactDetails();
} catch (NoSuchContactException e) {
finishOnUiThread();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
// contactAuthorId and contactName are expected to be set
private void displayContactDetails() {
runOnUiThreadUnlessDestroyed(() -> {
//noinspection ConstantConditions
toolbarAvatar.setImageDrawable(
new IdenticonDrawable(contactAuthorId.getBytes()));
toolbarTitle.setText(contactName.getValue());
});
}
private void displayContactOnlineStatus() {
runOnUiThreadUnlessDestroyed(() -> {
if (connectionRegistry.isConnected(contactId)) {
@@ -453,21 +452,9 @@ public class ConversationActivity extends BriarActivity
private void onNewPrivateMessage(PrivateMessageHeader h) {
runOnUiThreadUnlessDestroyed(() -> {
if (h instanceof PrivateRequest || h instanceof PrivateResponse) {
String cName = contactName.getValue();
if (cName == null) {
// Wait for the contact name to be loaded
contactName.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String cName) {
if (cName != null) {
addConversationItem(h.accept(visitor));
contactName.removeObserver(this);
}
}
});
} else {
addConversationItem(h.accept(visitor));
}
// contact name might not have been loaded
observeOnce(viewModel.getContactDisplayName(), this,
name -> addConversationItem(h.accept(visitor)));
} else {
addConversationItem(h.accept(visitor));
loadMessageText(h.getId());
@@ -605,6 +592,10 @@ public class ConversationActivity extends BriarActivity
});
}
private void enableAliasActionIfAvailable(MenuItem item) {
observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
}
private void enableIntroductionAction(MenuItem item) {
runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
}

View File

@@ -0,0 +1,115 @@
package org.briarproject.briar.android.contact;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Transformations;
import android.support.annotation.Nullable;
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.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.util.UiUtils;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
public class ConversationViewModel extends AndroidViewModel {
private static Logger LOG =
getLogger(ConversationViewModel.class.getName());
@DatabaseExecutor
private final Executor dbExecutor;
private final ContactManager contactManager;
@Nullable
private ContactId contactId = null;
private final MutableLiveData<Contact> contact = new MutableLiveData<>();
private final LiveData<AuthorId> contactAuthorId =
Transformations.map(contact, c -> c.getAuthor().getId());
private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName);
private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>();
@Inject
ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
ContactManager contactManager) {
super(application);
this.dbExecutor = dbExecutor;
this.contactManager = contactManager;
contactDeleted.setValue(false);
}
void setContactId(ContactId contactId) {
if (this.contactId == null) {
this.contactId = contactId;
loadContact();
} else if (!contactId.equals(this.contactId)) {
throw new IllegalStateException();
}
}
private void loadContact() {
dbExecutor.execute(() -> {
try {
long start = now();
Contact c =
contactManager.getContact(requireNonNull(contactId));
contact.postValue(c);
logDuration(LOG, "Loading contact", start);
} catch (NoSuchContactException e) {
contactDeleted.postValue(true);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void setContactAlias(String alias) {
dbExecutor.execute(() -> {
try {
contactManager.setContactAlias(requireNonNull(contactId),
alias.isEmpty() ? null : alias);
loadContact();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
LiveData<Contact> getContact() {
return contact;
}
LiveData<AuthorId> getContactAuthorId() {
return contactAuthorId;
}
LiveData<String> getContactDisplayName() {
return contactName;
}
LiveData<Boolean> isContactDeleted() {
return contactDeleted;
}
}

View File

@@ -3,6 +3,9 @@ package org.briarproject.briar.android.util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
@@ -11,6 +14,7 @@ import android.os.PowerManager;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager;
@@ -314,4 +318,21 @@ public class UiUtils {
keyEvent.getKeyCode() == KEYCODE_ENTER;
}
/**
* Observes the given {@link LiveData} until the first change.
* If the LiveData's value is available, the {@link Observer} will be
* called right away.
*/
@MainThread
public static <T> void observeOnce(LiveData<T> liveData,
LifecycleOwner owner, Observer<T> observer) {
liveData.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
observer.onChanged(t);
liveData.removeObserver(this);
}
});
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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 org.briarproject.briar.android.viewmodel;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
@Singleton
@NotNullByDefault
class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
ViewModelFactory(Map<Class<? extends ViewModel>,
Provider<ViewModel>> creators) {
this.creators = creators;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry :
creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException(
"unknown model class " + modelClass);
}
//noinspection unchecked
return (T) creator.get();
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.android.viewmodel;
import android.arch.lifecycle.ViewModel;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import dagger.MapKey;
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}

View File

@@ -0,0 +1,28 @@
package org.briarproject.briar.android.viewmodel;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import org.briarproject.briar.android.contact.ConversationViewModel;
import javax.inject.Singleton;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(ConversationViewModel.class)
abstract ViewModel bindConversationViewModel(
ConversationViewModel conversationViewModel);
@Binds
@Singleton
abstract ViewModelProvider.Factory bindViewModelFactory(
ViewModelFactory viewModelFactory);
}

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/titleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/set_contact_alias"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_large"
android:textStyle="bold"/>
<EditText
android:id="@+id/aliasEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/set_contact_alias_hint"
android:inputType="textPersonName"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_xxlarge"
android:layout_marginStart="@dimen/margin_xxlarge"
android:gravity="end"
android:orientation="horizontal">
<Button
android:id="@+id/cancelButton"
style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel"/>
<Button
android:id="@+id/setButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_alias"/>
</LinearLayout>
</LinearLayout>

View File

@@ -1,75 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:orientation="vertical">
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/link_warning_title"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_large"
android:textStyle="bold"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:text="@string/link_warning_intro"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"/>
<TextView
android:id="@+id/urlView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:typeface="monospace"
tools:text="http://very.bad.site.com"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:text="@string/link_warning_text"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/margin_large">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/link_warning_title"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_large"
android:textStyle="bold"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:text="@string/link_warning_intro"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"/>
<TextView
android:id="@+id/urlView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:textIsSelectable="true"
android:typeface="monospace"
tools:text="http://very.bad.site.com"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:text="@string/link_warning_text"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"/>
</LinearLayout>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/button_size">
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_xxlarge"
android:layout_marginStart="@dimen/margin_xxlarge"
android:gravity="end"
android:orientation="horizontal">
<Button
android:id="@+id/cancelButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@+id/openButton"
app:layout_constraintStart_toStartOf="parent"/>
android:text="@string/cancel"/>
<Button
android:id="@+id/openButton"
style="@style/BriarButtonFlat.Negative"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/link_warning_open_link"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cancelButton"/>
android:text="@string/link_warning_open_link"/>
</android.support.constraint.ConstraintLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -10,6 +10,12 @@
android:enabled="false"
app:showAsAction="never"/>
<item
android:id="@+id/action_set_alias"
android:title="@string/set_contact_alias"
android:enabled="false"
app:showAsAction="never"/>
<item
android:id="@+id/action_social_remove_person"
android:icon="@drawable/action_delete_white"

View File

@@ -127,6 +127,9 @@
<string name="date_no_private_messages">No messages.</string>
<string name="no_private_messages">No messages to show</string>
<string name="message_hint">Type message</string>
<string name="set_contact_alias">Change contact name</string>
<string name="set_contact_alias_hint">Contact name</string>
<string name="set_alias">Set</string>
<string name="delete_contact">Delete contact</string>
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>

View File

@@ -3,6 +3,7 @@ dependencyVerification {
'android.arch.core:common:1.1.1:common-1.1.1.jar:3a616a32f433e9e23f556b38575c31b013613d3ae85206263b7625fe1f4c151a',
'android.arch.core:runtime:1.1.1:runtime-1.1.1.aar:c3215aa5873311b3f88a6f4e4a3c25ad89971bc127de8c3e1291c57f93a05c39',
'android.arch.lifecycle:common:1.1.1:common-1.1.1.jar:8d378e88ebd5189e09eef623414812c868fd90aa519d6160e2311fb8b81cff56',
'android.arch.lifecycle:extensions:1.1.1:extensions-1.1.1.aar:429426b2feec2245ffc5e75b3b5309bedb36159cf06dc71843ae43526ac289b6',
'android.arch.lifecycle:livedata-core:1.1.1:livedata-core-1.1.1.aar:d6fdd8b985d6178d7ea2f16986a24e83f1bee936b74d43167c69e08d3cc12c50',
'android.arch.lifecycle:livedata:1.1.1:livedata-1.1.1.aar:50ab0490c1ff1a7cfb4e554032998b080888946d0dd424f39900efc4a1bcd750',
'android.arch.lifecycle:runtime:1.1.1:runtime-1.1.1.aar:c4e4be66c1b2f0abec593571454e1de14013f7e0f96bf2a9f212931a48cae550',