From 661aff6a1fa019734e92ecde67e0fdc1ad16cdb1 Mon Sep 17 00:00:00 2001 From: Christian Schneppe Date: Sat, 31 Mar 2018 15:03:40 +0200 Subject: save scroll state across rotations --- .../pixart/messenger/ui/ConversationActivity.java | 2 + .../pixart/messenger/ui/ConversationFragment.java | 158 +++++++++++++-------- .../de/pixart/messenger/ui/util/ScrollState.java | 71 +++++++++ 3 files changed, 168 insertions(+), 63 deletions(-) create mode 100644 src/main/java/de/pixart/messenger/ui/util/ScrollState.java diff --git a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java index 82f3f893d..0f6994b21 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java @@ -745,6 +745,8 @@ public class ConversationActivity extends XmppActivity implements OnConversation public void onConversationRead(Conversation conversation) { if (!mActivityPaused && pendingViewIntent.peek() == null) { xmppConnectionService.sendReadMarker(conversation); + } else { + Log.d(Config.LOGTAG, "ignoring read callback. mActivityPaused=" + Boolean.toString(mActivityPaused)); } } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index 3ea9bfe5b..f0e715d1f 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -30,7 +30,6 @@ import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; -import android.util.Pair; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; @@ -101,6 +100,7 @@ import de.pixart.messenger.ui.util.AttachmentTool; import de.pixart.messenger.ui.util.ConversationMenuConfigurator; import de.pixart.messenger.ui.util.PendingItem; import de.pixart.messenger.ui.util.PresenceSelector; +import de.pixart.messenger.ui.util.ScrollState; import de.pixart.messenger.ui.util.SendButtonAction; import de.pixart.messenger.ui.util.SendButtonTool; import de.pixart.messenger.ui.widget.EditMessage; @@ -148,6 +148,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private final PendingItem pendingConversationsUuid = new PendingItem<>(); private final PendingItem pendingExtras = new PendingItem<>(); private final PendingItem pendingTakePhotoUri = new PendingItem<>(); + private final PendingItem pendingScrollState = new PendingItem<>(); protected MessageAdapter messageListAdapter; private Conversation conversation; public FragmentConversationBinding binding; @@ -463,8 +464,41 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private String incomplete; private int lastCompletionCursor; private boolean firstWord = false; - private Message mPendingDownloadableMessage; + + public static void downloadFile(Activity activity, Message message) { + Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); + if (fragment != null && fragment instanceof ConversationFragment) { + ((ConversationFragment) fragment).startDownloadable(message); + return; + } + fragment = activity.getFragmentManager().findFragmentById(R.id.secondary_fragment); + if (fragment != null && fragment instanceof ConversationFragment) { + ((ConversationFragment) fragment).startDownloadable(message); + } + } + + public static Conversation getConversation(Activity activity) { + return getConversation(activity, R.id.secondary_fragment); + } + + private static Conversation getConversation(Activity activity, @IdRes int res) { + final Fragment fragment = activity.getFragmentManager().findFragmentById(res); + if (fragment != null && fragment instanceof ConversationFragment) { + return ((ConversationFragment) fragment).getConversation(); + } else { + return null; + } + } + + public static Conversation getConversationReliable(Activity activity) { + final Conversation conversation = getConversation(activity, R.id.secondary_fragment); + if (conversation != null) { + return conversation; + } + return getConversation(activity, R.id.main_fragment); + } + private TextWatcher mSearchTextWatcher = new TextWatcher() { @Override @@ -533,24 +567,39 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return -1; } - public Pair getScrollPosition() { - if (this.binding.messagesView.getCount() == 0 || - this.binding.messagesView.getLastVisiblePosition() == this.binding.messagesView.getCount() - 1) { + private ScrollState getScrollPosition(int pos, View view) { + final ListView listView = this.binding.messagesView; + if (listView.getCount() == 0 || listView.getLastVisiblePosition() == listView.getCount() - 1) { return null; } else { - final int pos = binding.messagesView.getFirstVisiblePosition(); - final View view = binding.messagesView.getChildAt(0); + //final int pos = listView.getFirstVisiblePosition(); + //final View view = listView.getChildAt(0); if (view == null) { return null; } else { - return new Pair<>(pos, view.getTop()); + return new ScrollState(pos, view.getTop()); } } } - public void setScrollPosition(Pair scrollPosition) { + private ScrollState getScrollPosition() { + final ListView listView = this.binding.messagesView; + if (listView.getCount() == 0 || listView.getLastVisiblePosition() == listView.getCount() - 1) { + return null; + } else { + final int pos = listView.getFirstVisiblePosition(); + final View view = listView.getChildAt(0); + if (view == null) { + return null; + } else { + return new ScrollState(pos, view.getTop()); + } + } + } + + private void setScrollPosition(ScrollState scrollPosition) { if (scrollPosition != null) { - this.binding.messagesView.setSelectionFromTop(scrollPosition.first, scrollPosition.second); + this.binding.messagesView.setSelectionFromTop(scrollPosition.position, scrollPosition.offset); } } @@ -1634,6 +1683,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke getActivity().invalidateOptionsMenu(); }); super.onResume(); + if (activity != null && this.conversation != null) { + activity.onConversationRead(this.conversation); + } } private void showErrorMessage(final Message message) { @@ -1738,18 +1790,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } - public static void downloadFile(Activity activity, Message message) { - Fragment fragment = activity.getFragmentManager().findFragmentById(R.id.main_fragment); - if (fragment != null && fragment instanceof ConversationFragment) { - ((ConversationFragment) fragment).startDownloadable(message); - return; - } - fragment = activity.getFragmentManager().findFragmentById(R.id.secondary_fragment); - if (fragment != null && fragment instanceof ConversationFragment) { - ((ConversationFragment) fragment).startDownloadable(message); - } - } - private void cancelTransmission(Message message) { Transferable transferable = message.getTransferable(); if (transferable != null) { @@ -1820,10 +1860,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke super.onSaveInstanceState(outState); if (conversation != null) { outState.putString(STATE_CONVERSATION_UUID, conversation.getUuid()); - Uri uri = pendingTakePhotoUri.pop(); + final Uri uri = pendingTakePhotoUri.pop(); if (uri != null) { outState.putString(STATE_PHOTO_URI, uri.toString()); } + final ScrollState scrollState = getScrollPosition(); + if (scrollState != null) { + outState.putParcelable(STATE_SCROLL_POSITION, scrollState); + } } } @@ -1840,6 +1884,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (takePhotoUri != null) { pendingTakePhotoUri.push(Uri.parse(takePhotoUri)); } + pendingScrollState.push(savedInstanceState.getParcelable(STATE_SCROLL_POSITION)); } } @@ -1917,14 +1962,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (conversation == null) { return false; } + final boolean hasChanged = this.conversation != null && this.conversation != conversation; this.conversation = conversation; //once we set the conversation all is good and it will automatically do the right thing in onStart() if (this.activity == null || this.binding == null) { return false; } - Log.d(Config.LOGTAG, "reInit(restore=" + Boolean.toString(restore) + ")"); + Log.d(Config.LOGTAG, "reInit(restore=" + Boolean.toString(restore) + ", hasChanged=" + Boolean.toString(hasChanged) + ")"); setupIme(); - if (!restore) { + if (!restore && hasChanged) { this.conversation.trim(); } @@ -1934,11 +1980,25 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.textinput.append(this.conversation.getNextMessage()); this.binding.textinput.setKeyboardListener(this); messageListAdapter.updatePreferences(); - this.binding.messagesView.setAdapter(messageListAdapter); + if (!restore && hasChanged) { + this.binding.messagesView.setAdapter(messageListAdapter); + } refresh(false); - refresh(); this.conversation.messagesLoaded.set(true); - final boolean isAtBottom; + if (!restore && hasChanged) { + synchronized (this.messageList) { + final Message first = conversation.getFirstUnreadMessage(); + final int bottom = Math.max(0, this.messageList.size() - 1); + final int pos; + if (first == null) { + pos = bottom; + } else { + int i = getIndexOf(first.getUuid(), this.messageList); + pos = i < 0 ? bottom : i; + } + this.binding.messagesView.setSelection(pos); + } + } this.binding.messagesView.setOnTouchListener(new OnSwipeTouchListener(getContext()) { @Override public void onSwipeRight() { @@ -1946,20 +2006,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke activity.onBackPressed(); } }); - synchronized (this.messageList) { - final Message first = conversation.getFirstUnreadMessage(); - final int bottom = Math.max(0, this.messageList.size() - 1); - final int pos; - if (first == null) { - pos = bottom; - } else { - int i = getIndexOf(first.getUuid(), this.messageList); - pos = i < 0 ? bottom : i; - } - this.binding.messagesView.setSelection(pos); - isAtBottom = pos == bottom; - } - activity.onConversationRead(this.conversation); //TODO if we only do this when this fragment is running on main it won't *bing* in tablet layout which might be unnecessary since we can *see* it activity.xmppConnectionService.getNotificationService().setOpenConversation(this.conversation); @@ -2649,7 +2695,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (lastHistoryMessage != null) { int pos = getIndexOf(lastHistoryMessage.getUuid(), messageList); - setScrollPosition(new Pair<>(pos, pos)); + setScrollPosition(getScrollPosition(pos, getView())); this.binding.messagesView.setSelection(pos); } return lastHistoryMessage; @@ -2678,6 +2724,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } reInit(conversation, true); + ScrollState scrollState = pendingScrollState.pop(); + if (scrollState != null) { + setScrollPosition(scrollState); + } } ActivityResult activityResult = postponedActivityResult.pop(); if (activityResult != null) { @@ -2689,28 +2739,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (postponedActivityResult.pop() != null) { Log.d(Config.LOGTAG, "cleared pending intent with unhandled result left"); } + pendingScrollState.pop(); + pendingTakePhotoUri.pop(); } - public static Conversation getConversation(Activity activity) { - return getConversation(activity, R.id.secondary_fragment); - } - - private static Conversation getConversation(Activity activity, @IdRes int res) { - final Fragment fragment = activity.getFragmentManager().findFragmentById(res); - if (fragment != null && fragment instanceof ConversationFragment) { - return ((ConversationFragment) fragment).getConversation(); - } else { - return null; - } - } - - public static Conversation getConversationReliable(Activity activity) { - final Conversation conversation = getConversation(activity, R.id.secondary_fragment); - if (conversation != null) { - return conversation; - } - return getConversation(activity, R.id.main_fragment); - } public Conversation getConversation() { return conversation; diff --git a/src/main/java/de/pixart/messenger/ui/util/ScrollState.java b/src/main/java/de/pixart/messenger/ui/util/ScrollState.java new file mode 100644 index 000000000..3a0e87900 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/ScrollState.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import android.os.Parcel; +import android.os.Parcelable; + +public class ScrollState implements Parcelable { + + public static final Creator CREATOR = new Creator() { + @Override + public ScrollState createFromParcel(Parcel in) { + return new ScrollState(in); + } + + @Override + public ScrollState[] newArray(int size) { + return new ScrollState[size]; + } + }; + public final int position; + public final int offset; + + private ScrollState(Parcel in) { + position = in.readInt(); + offset = in.readInt(); + } + + public ScrollState(int position, int offset) { + this.position = position; + this.offset = offset; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(position); + dest.writeInt(offset); + } +} \ No newline at end of file -- cgit v1.2.3