[android] do not show two private conversation onboardings at the same time

Checking for introduction onboarding is now done in the ViewModel
together with the image onboarding. The latter has preference. If both
could be shown, the introduction onboarding will be delayed to the next
time the user enters the conversation.
This commit is contained in:
Torsten Grote
2018-12-18 12:50:59 -02:00
committed by akwizgran
parent 16c701a71a
commit f73f8ca7e7
2 changed files with 81 additions and 66 deletions

View File

@@ -46,7 +46,6 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
@@ -126,7 +125,6 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
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;
@@ -141,11 +139,10 @@ public class ConversationActivity extends BriarActivity
TextCache, AttachmentCache, AttachImageListener {
public static final String CONTACT_ID = "briar.CONTACT_ID";
private static final int ONBOARDING_DELAY = 750;
private static final Logger LOG =
Logger.getLogger(ConversationActivity.class.getName());
private static final String SHOW_ONBOARDING_INTRODUCTION =
"showOnboardingIntroduction";
@Inject
AndroidNotificationManager notificationManager;
@@ -355,10 +352,19 @@ public class ConversationActivity extends BriarActivity
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.conversation_actions, menu);
enableIntroductionActionIfAvailable(
menu.findItem(R.id.action_introduction));
enableAliasActionIfAvailable(
menu.findItem(R.id.action_set_alias));
// enable introduction action if available
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
if (enable != null && enable) {
menu.findItem(R.id.action_introduction).setEnabled(true);
// show introduction onboarding, if needed
observeOnce(viewModel.showIntroductionOnboarding(), this,
this::showIntroductionOnboarding);
}
});
// enable alias action if available
observeOnce(viewModel.getContact(), this, contact -> {
menu.findItem(R.id.action_set_alias).setEnabled(true);
});
return super.onCreateOptionsMenu(menu);
}
@@ -713,32 +719,6 @@ public class ConversationActivity extends BriarActivity
});
}
private void enableIntroductionActionIfAvailable(MenuItem item) {
runOnDbThread(() -> {
try {
if (contactManager.getActiveContacts().size() > 1) {
enableIntroductionAction(item);
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
if (settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION,
true)) {
showIntroductionOnboarding();
}
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void enableAliasActionIfAvailable(MenuItem item) {
observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
}
private void enableIntroductionAction(MenuItem item) {
runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
}
private void showImageOnboarding(@Nullable Boolean show) {
if (show == null || !show) return;
// show onboarding only after the enter transition has ended
@@ -747,12 +727,15 @@ public class ConversationActivity extends BriarActivity
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
((TextAttachmentController) sendController)
.showImageOnboarding(this, () ->
viewModel.imageOnboardingSeen());
}, 750);
viewModel.onImageOnboardingSeen());
}, ONBOARDING_DELAY);
}
private void showIntroductionOnboarding() {
runOnUiThreadUnlessDestroyed(() -> {
private void showIntroductionOnboarding(@Nullable Boolean show) {
if (show == null || !show) return;
// show onboarding only after the enter transition has ended
// otherwise the tap target animation won't play
textInputView.postDelayed(() -> {
// find view of overflow icon
View target = null;
for (int i = 0; i < toolbar.getChildCount(); i++) {
@@ -770,7 +753,7 @@ public class ConversationActivity extends BriarActivity
PromptStateChangeListener listener = (prompt, state) -> {
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
introductionOnboardingSeen();
viewModel.onIntroductionOnboardingSeen();
}
};
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
@@ -782,19 +765,7 @@ public class ConversationActivity extends BriarActivity
ContextCompat.getColor(this, R.color.briar_primary))
.setPromptStateChangeListener(listener)
.show();
});
}
private void introductionOnboardingSeen() {
runOnDbThread(() -> {
try {
Settings settings = new Settings();
settings.putBoolean(SHOW_ONBOARDING_INTRODUCTION, false);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}, ONBOARDING_DELAY);
}
@Override

View File

@@ -38,6 +38,7 @@ import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -62,6 +63,8 @@ public class ConversationViewModel extends AndroidViewModel {
getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE =
"showOnboardingImage";
private static final String SHOW_ONBOARDING_INTRODUCTION =
"showOnboardingIntroduction";
@DatabaseExecutor
private final Executor dbExecutor;
@@ -86,6 +89,10 @@ public class ConversationViewModel extends AndroidViewModel {
new MutableLiveData<>();
private final MutableLiveData<Boolean> showImageOnboarding =
new MutableLiveData<>();
private final MutableLiveData<Boolean> showIntroductionOnboarding =
new MutableLiveData<>();
private final MutableLiveData<Boolean> showIntroductionAction =
new MutableLiveData<>();
private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>();
private final MutableLiveData<GroupId> messagingGroupId =
@@ -115,25 +122,28 @@ public class ConversationViewModel extends AndroidViewModel {
contactDeleted.setValue(false);
}
/**
* Setting the {@link ContactId} automatically triggers loading of other
* data.
*/
void setContactId(ContactId contactId) {
if (this.contactId == null) {
this.contactId = contactId;
loadContact();
loadContact(contactId);
} else if (!contactId.equals(this.contactId)) {
throw new IllegalStateException();
}
}
private void loadContact() {
private void loadContact(ContactId contactId) {
dbExecutor.execute(() -> {
try {
long start = now();
Contact c =
contactManager.getContact(requireNonNull(contactId));
Contact c = contactManager.getContact(contactId);
contact.postValue(c);
logDuration(LOG, "Loading contact", start);
start = now();
checkImageSupport(c.getId());
checkFeaturesAndOnboarding(contactId);
logDuration(LOG, "Checking for image support", start);
} catch (NoSuchContactException e) {
contactDeleted.postValue(true);
@@ -148,7 +158,7 @@ public class ConversationViewModel extends AndroidViewModel {
try {
contactManager.setContactAlias(requireNonNull(contactId),
alias.isEmpty() ? null : alias);
loadContact();
loadContact(contactId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
@@ -177,28 +187,54 @@ public class ConversationViewModel extends AndroidViewModel {
}
@DatabaseExecutor
private void checkImageSupport(ContactId c) throws DbException {
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
// check if images are supported
int minorVersion = db.transactionWithResult(true, txn ->
clientVersioningManager
.getClientMinorVersion(txn, c, CLIENT_ID, 0));
// support was added in 0.1
boolean imagesSupported = minorVersion == 1;
imageSupport.postValue(imagesSupported);
if (!imagesSupported) return;
// check if we should show onboarding, only if images are supported
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
if (settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
// check if introductions are supported
Collection<Contact> contacts = contactManager.getActiveContacts();
boolean introductionSupported = contacts.size() > 1;
showIntroductionAction.postValue(introductionSupported);
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
if (imagesSupported &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
// check if we should show onboarding, only if images are supported
showImageOnboarding.postValue(true);
// allow observer to stop listening for changes
showIntroductionOnboarding.postValue(false);
} else {
// allow observer to stop listening for changes
showImageOnboarding.postValue(false);
// we only show one onboarding dialog at a time
if (introductionSupported &&
settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, true)) {
showIntroductionOnboarding.postValue(true);
} else {
// allow observer to stop listening for changes
showIntroductionOnboarding.postValue(false);
}
}
}
void imageOnboardingSeen() {
void onImageOnboardingSeen() {
onOnboardingSeen(SHOW_ONBOARDING_IMAGE);
}
void onIntroductionOnboardingSeen() {
onOnboardingSeen(SHOW_ONBOARDING_INTRODUCTION);
}
private void onOnboardingSeen(String key) {
dbExecutor.execute(() -> {
try {
Settings settings = new Settings();
settings.putBoolean(SHOW_ONBOARDING_IMAGE, false);
settings.putBoolean(key, false);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -322,6 +358,14 @@ public class ConversationViewModel extends AndroidViewModel {
return showImageOnboarding;
}
LiveData<Boolean> showIntroductionOnboarding() {
return showIntroductionOnboarding;
}
LiveData<Boolean> showIntroductionAction() {
return showIntroductionAction;
}
LiveData<Boolean> isContactDeleted() {
return contactDeleted;
}