diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 2d8bc6855..fed7d7253 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -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' diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 6971b49a6..c70a13c8e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -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); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index bbfb919ce..c993eacaa 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -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 { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index c327d64ae..00feafdaf 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -15,6 +15,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); + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/AliasDialogFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/AliasDialogFragment.java new file mode 100644 index 000000000..854b14222 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/AliasDialogFragment.java @@ -0,0 +1,85 @@ +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.bramble.api.contact.ContactId; +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 ContactId contactId; + private EditText aliasEditText; + + public static AliasDialogFragment newInstance(ContactId id) { + AliasDialogFragment f = new AliasDialogFragment(); + + Bundle args = new Bundle(); + args.putInt("contactId", id.getInt()); + f.setArguments(args); + + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getArguments() == null) throw new IllegalArgumentException(); + int contactIdInt = getArguments().getInt("contactId", -1); + if (contactIdInt == -1) throw new IllegalArgumentException(); + contactId = new ContactId(contactIdInt); + + setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme); + + ((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(contactId, + aliasEditText.getText().toString()); + getDialog().dismiss(); + }); + + Button cancelButton = v.findViewById(R.id.cancelButton); + cancelButton.setOnClickListener(v1 -> getDialog().cancel()); + + return v; + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java index 423f47ec9..f9c9d1cb3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationActivity.java @@ -1,7 +1,7 @@ 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 +23,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 +33,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; @@ -105,6 +103,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 +130,8 @@ public class ConversationActivity extends BriarActivity Executor cryptoExecutor; private final Map textCache = new ConcurrentHashMap<>(); - private final MutableLiveData contactName = new MutableLiveData<>(); + private ConversationViewModel viewModel; private ConversationVisitor visitor; private ConversationAdapter adapter; private Toolbar toolbar; @@ -163,11 +162,11 @@ 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") @@ -176,6 +175,9 @@ public class ConversationActivity extends BriarActivity setSceneTransitionAnimation(); super.onCreate(state); + viewModel = ViewModelProviders.of(this, viewModelFactory) + .get(ConversationViewModel.class); + Intent i = getIntent(); int id = i.getIntExtra(CONTACT_ID, -1); if (id == -1) throw new IllegalStateException(); @@ -185,16 +187,28 @@ public class ConversationActivity extends BriarActivity // 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); - } + if (toolbar == null) throw new AssertionError(); + toolbarAvatar = toolbar.findViewById(R.id.contactAvatar); + toolbarStatus = toolbar.findViewById(R.id.contactStatus); + toolbarTitle = toolbar.findViewById(R.id.contactName); + + observeOnce(viewModel.getContactAuthorId(), this, authorId -> { + toolbarAvatar.setImageDrawable( + new IdenticonDrawable(authorId.getBytes())); + }); + viewModel.getContactDisplayName().observe(this, contactName -> { + toolbarTitle.setText(contactName); + }); + viewModel.isContactDeleted().observe(this, deleted -> { + if (deleted) finish(); + }); + viewModel.loadContact(contactId); 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 +243,8 @@ public class ConversationActivity extends BriarActivity notificationManager.blockContactNotification(contactId); notificationManager.clearContactNotification(contactId); displayContactOnlineStatus(); - loadContactDetailsAndMessages(); + observeOnce(viewModel.getContactDisplayName(), this, + name -> loadMessages()); list.startPeriodicUpdate(); } @@ -249,6 +264,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 +283,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(contactId).show( + getSupportFragmentManager(), AliasDialogFragment.TAG); + return true; case R.id.action_social_remove_person: askToRemoveContact(); return true; @@ -274,36 +295,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 +444,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() { - @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 +584,12 @@ 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)); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationViewModel.java new file mode 100644 index 000000000..1141bc23c --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/ConversationViewModel.java @@ -0,0 +1,104 @@ +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.NonNull; + +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.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +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.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; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class ConversationViewModel extends AndroidViewModel { + + private static Logger LOG = + getLogger(ConversationViewModel.class.getName()); + + @DatabaseExecutor + private final Executor dbExecutor; + private final ContactManager contactManager; + + private final MutableLiveData contact = new MutableLiveData<>(); + private final LiveData contactAuthorId = + Transformations.map(contact, c -> c.getAuthor().getId()); + private final LiveData contactName = + Transformations.map(contact, UiUtils::getContactDisplayName); + private final MutableLiveData contactDeleted = + new MutableLiveData<>(); + + @Inject + public ConversationViewModel(@NonNull Application application, + @DatabaseExecutor Executor dbExecutor, + ContactManager contactManager) { + super(application); + this.dbExecutor = dbExecutor; + this.contactManager = contactManager; + contactDeleted.setValue(false); + } + + void loadContact(ContactId contactId) { + dbExecutor.execute(() -> { + try { + long start = now(); + contact.postValue(contactManager.getContact(contactId)); + logDuration(LOG, "Loading contact", start); + } catch (NoSuchContactException e) { + contactDeleted.postValue(true); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + void setContactAlias(ContactId contactId, String alias) { + dbExecutor.execute(() -> { + try { + contactManager.setContactAlias(contactId, + alias.isEmpty() ? null : alias); + // TODO also reload the conversation + loadContact(contactId); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + LiveData getContact() { + return contact; + } + + LiveData getContactAuthorId() { + return contactAuthorId; + } + + LiveData getContactDisplayName() { + return contactName; + } + + LiveData isContactDeleted() { + return contactDeleted; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/Runnable.java b/briar-android/src/main/java/org/briarproject/briar/android/util/Runnable.java new file mode 100644 index 000000000..c019e631e --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/Runnable.java @@ -0,0 +1,13 @@ +package org.briarproject.briar.android.util; + +import android.support.annotation.Nullable; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + + +@NotNullByDefault +public interface Runnable { + + void run(@Nullable T t); + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index 27c24083c..3af664ffa 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -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; @@ -174,7 +178,7 @@ public class UiUtils { *

* Attention: This assumes that there's only one link in the text. */ - public static void onSingleLinkClick(TextView textView, Runnable runnable) { + public static void onSingleLinkClick(TextView textView, java.lang.Runnable runnable) { SpannableStringBuilder ssb = new SpannableStringBuilder(textView.getText()); ClickableSpan[] spans = @@ -314,4 +318,22 @@ 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 Runnable} will be executed right away. + */ + @MainThread + public static void observeOnce(LiveData liveData, + LifecycleOwner owner, + Runnable function) { + liveData.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable T t) { + function.run(t); + liveData.removeObserver(this); + } + }); + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelFactory.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelFactory.java new file mode 100644 index 000000000..5eb220b6c --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelFactory.java @@ -0,0 +1,65 @@ +/* + * 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 javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +@Singleton +@NotNullByDefault +public class ViewModelFactory implements ViewModelProvider.Factory { + + private final Map, Provider> creators; + + @Inject + public ViewModelFactory( + Map, Provider> creators) { + this.creators = creators; + } + + @Override + public T create(Class modelClass) { + Provider creator = creators.get(modelClass); + if (creator == null) { + for (Map.Entry, Provider> entry : creators + .entrySet()) { + if (modelClass.isAssignableFrom(entry.getKey())) { + creator = entry.getValue(); + break; + } + } + } + if (creator == null) { + throw new IllegalArgumentException( + "unknown model class " + modelClass); + } + try { + //noinspection unchecked + return (T) creator.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelKey.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelKey.java new file mode 100644 index 000000000..bd4f3d650 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelKey.java @@ -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 value(); +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java new file mode 100644 index 000000000..0bd6e6aab --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelModule.java @@ -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 userViewModel); + + @Binds + @Singleton + abstract ViewModelProvider.Factory bindViewModelFactory( + ViewModelFactory factory); + +} diff --git a/briar-android/src/main/res/layout/fragment_alias_dialog.xml b/briar-android/src/main/res/layout/fragment_alias_dialog.xml new file mode 100644 index 000000000..d4846cf11 --- /dev/null +++ b/briar-android/src/main/res/layout/fragment_alias_dialog.xml @@ -0,0 +1,54 @@ + + + + + + + + + +