From 374fc7035b03715e3aecb79b2542675cb168f1ed Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 11 Dec 2018 13:08:26 -0200 Subject: [PATCH 1/7] [android] Save and restore list position of conversation across restarts --- .../conversation/ConversationActivity.java | 35 ++++++++++++++++--- .../main/res/layout/activity_conversation.xml | 3 +- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index c98e774cf..c7ec000e4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -8,6 +8,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.Parcelable; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.design.widget.Snackbar; @@ -193,6 +194,8 @@ public class ConversationActivity extends BriarActivity ViewModelProvider.Factory viewModelFactory; private volatile ContactId contactId; + @Nullable + private Parcelable layoutManagerState; private final Observer contactNameObserver = name -> { requireNonNull(name); @@ -317,6 +320,21 @@ public class ConversationActivity extends BriarActivity list.stopPeriodicUpdate(); } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (layoutManager != null) { + layoutManagerState = layoutManager.onSaveInstanceState(); + outState.putParcelable("layoutManager", layoutManagerState); + } + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + layoutManagerState = savedInstanceState.getParcelable("layoutManager"); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar @@ -436,8 +454,12 @@ public class ConversationActivity extends BriarActivity List items = createItems(headers); adapter.addAll(items); list.showData(); - // Scroll to the bottom - list.scrollToPosition(adapter.getItemCount() - 1); + if (layoutManagerState == null) { + // Scroll to the bottom + list.scrollToPosition(adapter.getItemCount() - 1); + } else { + layoutManager.onRestoreInstanceState(layoutManagerState); + } } else { LOG.info("Concurrent update, reloading"); loadMessages(); @@ -479,7 +501,8 @@ public class ConversationActivity extends BriarActivity adapter.getMessageItem(m); if (pair != null) { pair.getSecond().setText(text); - boolean bottom = adapter.isScrolledToBottom(layoutManager); + boolean bottom = layoutManagerState == null && + adapter.isScrolledToBottom(layoutManager); adapter.notifyItemChanged(pair.getFirst()); if (bottom) list.scrollToPosition(adapter.getItemCount() - 1); } @@ -510,7 +533,8 @@ public class ConversationActivity extends BriarActivity adapter.getMessageItem(m); if (pair != null) { pair.getSecond().setAttachments(items); - boolean bottom = adapter.isScrolledToBottom(layoutManager); + boolean bottom = layoutManagerState == null && + adapter.isScrolledToBottom(layoutManager); adapter.notifyItemChanged(pair.getFirst()); if (bottom) list.scrollToPosition(adapter.getItemCount() - 1); } @@ -561,7 +585,8 @@ public class ConversationActivity extends BriarActivity private void addConversationItem(ConversationItem item) { runOnUiThreadUnlessDestroyed(() -> { - boolean bottom = adapter.isScrolledToBottom(layoutManager); + boolean bottom = layoutManagerState == null && + adapter.isScrolledToBottom(layoutManager); adapter.incrementRevision(); adapter.add(item); if (bottom) list.scrollToPosition(adapter.getItemCount() - 1); diff --git a/briar-android/src/main/res/layout/activity_conversation.xml b/briar-android/src/main/res/layout/activity_conversation.xml index 1f78f03d1..d03850dee 100644 --- a/briar-android/src/main/res/layout/activity_conversation.xml +++ b/briar-android/src/main/res/layout/activity_conversation.xml @@ -49,7 +49,8 @@ android:id="@+id/conversationView" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="2"/> + android:layout_weight="2" + app:scrollToEnd="false"/> Date: Tue, 11 Dec 2018 15:23:30 -0200 Subject: [PATCH 2/7] [android] Fix enter transition to fullscreen ImageActivity --- .../briar/android/conversation/ImageActivity.java | 9 +++++++-- .../briar/android/conversation/ImageFragment.java | 12 +++++++++--- .../briar/android/conversation/ImageViewHolder.java | 4 ++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java index 811bf11e3..f7da8d5c3 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java @@ -82,7 +82,7 @@ public class ImageActivity extends BriarActivity super.onCreate(state); // Transitions - supportPostponeEnterTransition(); + if (state == null) supportPostponeEnterTransition(); Window window = getWindow(); if (SDK_INT >= 21) { Transition transition = new Fade(); @@ -298,13 +298,18 @@ public class ImageActivity extends BriarActivity private class ImagePagerAdapter extends FragmentStatePagerAdapter { + private boolean isFirst = true; + private ImagePagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { - return ImageFragment.newInstance(attachments.get(position)); + Fragment f = ImageFragment + .newInstance(attachments.get(position), isFirst); + isFirst = false; + return f; } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java index bf7ff5791..6ab0d0b04 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageFragment.java @@ -35,17 +35,21 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME @ParametersAreNonnullByDefault public class ImageFragment extends Fragment { + private final static String IS_FIRST = "isFirst"; + @Inject ViewModelProvider.Factory viewModelFactory; private AttachmentItem attachment; + private boolean isFirst; private ImageViewModel viewModel; private PhotoView photoView; - static ImageFragment newInstance(AttachmentItem a) { + static ImageFragment newInstance(AttachmentItem a, boolean isFirst) { ImageFragment f = new ImageFragment(); Bundle args = new Bundle(); args.putParcelable(ATTACHMENT_POSITION, a); + args.putBoolean(IS_FIRST, isFirst); f.setArguments(args); return f; } @@ -63,6 +67,7 @@ public class ImageFragment extends Fragment { Bundle args = requireNonNull(getArguments()); attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION)); + isFirst = args.getBoolean(IS_FIRST); } @Nullable @@ -86,7 +91,7 @@ public class ImageFragment extends Fragment { public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - if (getActivity() != null) + if (getActivity() != null && isFirst) getActivity().supportStartPostponedEnterTransition(); return false; } @@ -105,8 +110,9 @@ public class ImageFragment extends Fragment { if (viewModel.isOverlappingToolbar(photoView, resource)) { photoView.setScaleType(FIT_START); } - if (getActivity() != null) + if (getActivity() != null && isFirst) { getActivity().supportStartPostponedEnterTransition(); + } return false; } }; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java index 301fa3c13..f76e3c2ec 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageViewHolder.java @@ -15,6 +15,7 @@ import org.briarproject.briar.android.conversation.glide.BriarImageTransformatio import org.briarproject.briar.android.conversation.glide.GlideApp; import org.briarproject.briar.android.conversation.glide.Radii; +import static android.os.Build.VERSION.SDK_INT; import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE; import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; @@ -42,6 +43,9 @@ class ImageViewHolder extends ViewHolder { } else { setImageViewDimensions(attachment, single, needsStretch); loadImage(attachment, r); + if (SDK_INT >= 21) { + imageView.setTransitionName(attachment.getTransitionName()); + } } } From ed8c09282db2ffa3a263db30cc2e7f3cd5d65b1a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 11 Dec 2018 16:09:36 -0200 Subject: [PATCH 3/7] [android] enable image shared element transition for API 21+22 There's an Android framework bug (#224270) on these APIs that causes a NPE when the shared element is not visible anymore when returning. Since we know restore the list position, the shared element should be visible and thus not produce NPEs anymore. --- .../android/conversation/ConversationActivity.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index c7ec000e4..9512a6e2c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -853,15 +853,11 @@ public class ConversationActivity extends BriarActivity i.putExtra(ATTACHMENT_POSITION, attachments.indexOf(item)); i.putExtra(NAME, name); i.putExtra(DATE, messageItem.getTime()); - if (SDK_INT >= 23) { - String transitionName = item.getTransitionName(); - ActivityOptionsCompat options = - makeSceneTransitionAnimation(this, view, transitionName); - ActivityCompat.startActivity(this, i, options.toBundle()); - } else { - // work-around for android bug #224270 - startActivity(i); - } + // restoring list position should not trigger android bug #224270 + String transitionName = item.getTransitionName(); + ActivityOptionsCompat options = + makeSceneTransitionAnimation(this, view, transitionName); + ActivityCompat.startActivity(this, i, options.toBundle()); } @DatabaseExecutor From a53345a3c9cd2c4d1fe32709460ed7401f2b817c Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 18 Dec 2018 13:47:49 -0200 Subject: [PATCH 4/7] [android] scroll down when new messages arrive while conversation is visible Also shows new message notification when ConversationActivity is paused --- .../conversation/ConversationActivity.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index 9512a6e2c..d0fde5de8 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -103,6 +103,7 @@ import im.delight.android.identicons.IdenticonDrawable; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener; +import static android.arch.lifecycle.Lifecycle.State.RESUMED; import static android.os.Build.VERSION.SDK_INT; import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static android.support.v4.view.ViewCompat.setTransitionName; @@ -294,8 +295,6 @@ public class ConversationActivity extends BriarActivity public void onStart() { super.onStart(); eventBus.addListener(this); - notificationManager.blockContactNotification(contactId); - notificationManager.clearContactNotification(contactId); displayContactOnlineStatus(); viewModel.getContactDisplayName().observe(this, contactNameObserver); list.startPeriodicUpdate(); @@ -304,6 +303,9 @@ public class ConversationActivity extends BriarActivity @Override public void onResume() { super.onResume(); + // TODO move back to onStart() when we have unread msg indicators + notificationManager.blockContactNotification(contactId); + notificationManager.clearContactNotification(contactId); // Trigger loading of contact data, noop if data was loaded already. // // We can only start loading data *after* we are sure @@ -311,11 +313,17 @@ public class ConversationActivity extends BriarActivity if (signedIn()) viewModel.setContactId(contactId); } + @Override + protected void onPause() { + super.onPause(); + // TODO move back to onStop() when we have unread msg indicators + notificationManager.unblockContactNotification(contactId); + } + @Override public void onStop() { super.onStop(); eventBus.removeListener(this); - notificationManager.unblockContactNotification(contactId); viewModel.getContactDisplayName().removeObserver(contactNameObserver); list.stopPeriodicUpdate(); } @@ -501,10 +509,12 @@ public class ConversationActivity extends BriarActivity adapter.getMessageItem(m); if (pair != null) { pair.getSecond().setText(text); - boolean bottom = layoutManagerState == null && + boolean shouldScroll = layoutManagerState == null && adapter.isScrolledToBottom(layoutManager); adapter.notifyItemChanged(pair.getFirst()); - if (bottom) list.scrollToPosition(adapter.getItemCount() - 1); + if (shouldScroll) { + list.scrollToPosition(adapter.getItemCount() - 1); + } } }); } @@ -533,10 +543,12 @@ public class ConversationActivity extends BriarActivity adapter.getMessageItem(m); if (pair != null) { pair.getSecond().setAttachments(items); - boolean bottom = layoutManagerState == null && + boolean shouldScroll = layoutManagerState == null && adapter.isScrolledToBottom(layoutManager); adapter.notifyItemChanged(pair.getFirst()); - if (bottom) list.scrollToPosition(adapter.getItemCount() - 1); + if (shouldScroll) { + list.scrollToPosition(adapter.getItemCount() - 1); + } } }); } @@ -585,11 +597,11 @@ public class ConversationActivity extends BriarActivity private void addConversationItem(ConversationItem item) { runOnUiThreadUnlessDestroyed(() -> { - boolean bottom = layoutManagerState == null && - adapter.isScrolledToBottom(layoutManager); + // whenever the activity is shown, we'll scroll down for new msgs + boolean shouldScroll = getLifecycle().getCurrentState() == RESUMED; adapter.incrementRevision(); adapter.add(item); - if (bottom) list.scrollToPosition(adapter.getItemCount() - 1); + if (shouldScroll) list.scrollToPosition(adapter.getItemCount() - 1); }); } From ef998577db736ba1cf921672f035c0183cb99a11 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Mon, 24 Dec 2018 12:26:04 -0200 Subject: [PATCH 5/7] [android] add nullability annotations to ImageActivity --- .../briar/android/conversation/ImageActivity.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java index f7da8d5c3..9658a9ece 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java @@ -27,6 +27,8 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.Window; import android.widget.TextView; +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; @@ -55,6 +57,8 @@ import static java.util.Objects.requireNonNull; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT; import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute; +@MethodsNotNullByDefault +@ParametersNotNullByDefault public class ImageActivity extends BriarActivity implements PullDownLayout.Callback, OnGlobalLayoutListener { @@ -174,9 +178,11 @@ public class ImageActivity extends BriarActivity } @Override - protected void onActivityResult(int request, int result, Intent data) { + protected void onActivityResult(int request, int result, + @Nullable Intent data) { super.onActivityResult(request, result, data); - if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK) { + if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK && + data != null) { viewModel.saveImage(getVisibleAttachment(), data.getData()); } } From 045fcfc5fa44e5fa2f3cd9d258a21f486d907876 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 4 Jan 2019 17:25:39 +0000 Subject: [PATCH 6/7] Remove translucent window effect. --- briar-android/src/main/AndroidManifest.xml | 6 ++++-- .../android/conversation/ConversationActivity.java | 13 +++---------- .../briar/android/conversation/ImageActivity.java | 2 -- briar-android/src/main/res/values/themes.xml | 4 +--- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index 7e3f9f712..5708cbd00 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ @@ -29,7 +30,8 @@ android:label="@string/app_name" android:logo="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/BriarTheme"> + android:theme="@style/BriarTheme" + tools:ignore="GoogleAppIndexingWarning"> + android:theme="@style/BriarTheme.ActionBarOverlay"> diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index d0fde5de8..f74aaa583 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -295,6 +295,8 @@ public class ConversationActivity extends BriarActivity public void onStart() { super.onStart(); eventBus.addListener(this); + notificationManager.blockContactNotification(contactId); + notificationManager.clearContactNotification(contactId); displayContactOnlineStatus(); viewModel.getContactDisplayName().observe(this, contactNameObserver); list.startPeriodicUpdate(); @@ -303,9 +305,6 @@ public class ConversationActivity extends BriarActivity @Override public void onResume() { super.onResume(); - // TODO move back to onStart() when we have unread msg indicators - notificationManager.blockContactNotification(contactId); - notificationManager.clearContactNotification(contactId); // Trigger loading of contact data, noop if data was loaded already. // // We can only start loading data *after* we are sure @@ -313,17 +312,11 @@ public class ConversationActivity extends BriarActivity if (signedIn()) viewModel.setContactId(contactId); } - @Override - protected void onPause() { - super.onPause(); - // TODO move back to onStop() when we have unread msg indicators - notificationManager.unblockContactNotification(contactId); - } - @Override public void onStop() { super.onStop(); eventBus.removeListener(this); + notificationManager.unblockContactNotification(contactId); viewModel.getContactDisplayName().removeObserver(contactNameObserver); list.stopPeriodicUpdate(); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java index 9658a9ece..6c165134d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ImageActivity.java @@ -101,7 +101,6 @@ public class ImageActivity extends BriarActivity // inflate layout setContentView(R.layout.activity_image); layout = findViewById(R.id.layout); - layout.getBackground().setAlpha(255); layout.setCallback(this); layout.getViewTreeObserver().addOnGlobalLayoutListener(this); @@ -196,7 +195,6 @@ public class ImageActivity extends BriarActivity @Override public void onPull(float progress) { - layout.getBackground().setAlpha(Math.round((1 - progress) * 255)); } @Override diff --git a/briar-android/src/main/res/values/themes.xml b/briar-android/src/main/res/values/themes.xml index 8f54152ce..1ede39934 100644 --- a/briar-android/src/main/res/values/themes.xml +++ b/briar-android/src/main/res/values/themes.xml @@ -18,9 +18,7 @@ @style/BriarToolbar - From 418451cbd972a0ec61c99e1b8d3b6aff96d30663 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 8 Jan 2019 13:34:37 +0000 Subject: [PATCH 7/7] Use consistent conditions to decide whether to scroll. --- .../conversation/AttachmentController.java | 5 +- .../conversation/ConversationActivity.java | 94 +++++++++++-------- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java index 9285f9a8d..3f1c55d6d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java @@ -86,8 +86,7 @@ class AttachmentController { List> attachments = new ArrayList<>(headers.size()); for (AttachmentHeader h : headers) { - Attachment a = - messagingManager.getAttachment(h.getMessageId()); + Attachment a = messagingManager.getAttachment(h.getMessageId()); attachments.add(new Pair<>(h, a)); } logDuration(LOG, "Loading attachment", start); @@ -96,7 +95,7 @@ class AttachmentController { /** * Creates {@link AttachmentItem}s from the passed headers and Attachments. - * + *

* Note: This closes the {@link Attachment}'s {@link InputStream}. */ List getAttachmentItems( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index f74aaa583..2c0c5c72f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -103,7 +103,7 @@ import im.delight.android.identicons.IdenticonDrawable; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener; -import static android.arch.lifecycle.Lifecycle.State.RESUMED; +import static android.arch.lifecycle.Lifecycle.State.STARTED; import static android.os.Build.VERSION.SDK_INT; import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static android.support.v4.view.ViewCompat.setTransitionName; @@ -269,6 +269,12 @@ public class ConversationActivity extends BriarActivity textInputView.setSendController(sendController); textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH); textInputView.setEnabled(false); + textInputView.addOnKeyboardShownListener(this::scrollToBottom); + } + + private void scrollToBottom() { + int items = adapter.getItemCount(); + if (items > 0) list.scrollToPosition(items - 1); } @Override @@ -408,33 +414,10 @@ public class ConversationActivity extends BriarActivity Long.compare(b.getTimestamp(), a.getTimestamp())); if (!sorted.isEmpty()) { // If the latest header is a private message, eagerly load - // its text so we can set the scroll position correctly + // its size so we can set the scroll position correctly ConversationMessageHeader latest = sorted.get(0); if (latest instanceof PrivateMessageHeader) { - MessageId id = latest.getId(); - PrivateMessageHeader h = (PrivateMessageHeader) latest; - if (h.hasText()) { - String text = textCache.get(id); - if (text == null) { - LOG.info( - "Eagerly loading text of latest message"); - text = messagingManager.getMessageText(id); - textCache.put(id, text); - } - } - if (h.getAttachmentHeaders().size() == 1) { - List items = - attachmentController.get(id); - if (items == null) { - LOG.info( - "Eagerly loading image size for latest message"); - items = attachmentController.getAttachmentItems( - attachmentController - .getMessageAttachments( - h.getAttachmentHeaders())); - attachmentController.put(id, items); - } - } + eagerlyLoadMessageSize((PrivateMessageHeader) latest); } } displayMessages(revision, sorted); @@ -446,6 +429,32 @@ public class ConversationActivity extends BriarActivity }); } + private void eagerlyLoadMessageSize(PrivateMessageHeader h) + throws DbException { + MessageId id = h.getId(); + // If the message has text, load it + if (h.hasText()) { + String text = textCache.get(id); + if (text == null) { + LOG.info("Eagerly loading text for latest message"); + text = messagingManager.getMessageText(id); + textCache.put(id, text); + } + } + // If the message has a single image, load its size - for multiple + // images we use a grid so the size is fixed + if (h.getAttachmentHeaders().size() == 1) { + List items = attachmentController.get(id); + if (items == null) { + LOG.info("Eagerly loading image size for latest message"); + items = attachmentController.getAttachmentItems( + attachmentController.getMessageAttachments( + h.getAttachmentHeaders())); + attachmentController.put(id, items); + } + } + } + private void displayMessages(int revision, Collection headers) { runOnUiThreadUnlessDestroyed(() -> { @@ -456,9 +465,9 @@ public class ConversationActivity extends BriarActivity adapter.addAll(items); list.showData(); if (layoutManagerState == null) { - // Scroll to the bottom - list.scrollToPosition(adapter.getItemCount() - 1); + scrollToBottom(); } else { + // Restore the previous scroll position layoutManager.onRestoreInstanceState(layoutManagerState); } } else { @@ -501,17 +510,21 @@ public class ConversationActivity extends BriarActivity Pair pair = adapter.getMessageItem(m); if (pair != null) { + boolean scroll = shouldScrollWhenUpdatingMessage(); pair.getSecond().setText(text); - boolean shouldScroll = layoutManagerState == null && - adapter.isScrolledToBottom(layoutManager); adapter.notifyItemChanged(pair.getFirst()); - if (shouldScroll) { - list.scrollToPosition(adapter.getItemCount() - 1); - } + if (scroll) scrollToBottom(); } }); } + // When a message's text or attachments are loaded, scroll to the bottom + // if the conversation is visible and we were previously at the bottom + private boolean shouldScrollWhenUpdatingMessage() { + return getLifecycle().getCurrentState().isAtLeast(STARTED) + && adapter.isScrolledToBottom(layoutManager); + } + private void loadMessageAttachments(MessageId messageId, List headers) { runOnDbThread(() -> { @@ -535,13 +548,10 @@ public class ConversationActivity extends BriarActivity Pair pair = adapter.getMessageItem(m); if (pair != null) { + boolean scroll = shouldScrollWhenUpdatingMessage(); pair.getSecond().setAttachments(items); - boolean shouldScroll = layoutManagerState == null && - adapter.isScrolledToBottom(layoutManager); adapter.notifyItemChanged(pair.getFirst()); - if (shouldScroll) { - list.scrollToPosition(adapter.getItemCount() - 1); - } + if (scroll) scrollToBottom(); } }); } @@ -590,11 +600,13 @@ public class ConversationActivity extends BriarActivity private void addConversationItem(ConversationItem item) { runOnUiThreadUnlessDestroyed(() -> { - // whenever the activity is shown, we'll scroll down for new msgs - boolean shouldScroll = getLifecycle().getCurrentState() == RESUMED; adapter.incrementRevision(); adapter.add(item); - if (shouldScroll) list.scrollToPosition(adapter.getItemCount() - 1); + // When adding a new message, scroll to the bottom if the + // conversation is visible, even if we're not currently at + // the bottom + if (getLifecycle().getCurrentState().isAtLeast(STARTED)) + scrollToBottom(); }); }