Return to previous scroll position when returning to a conversation.
This commit is contained in:
parent
a9ea1d7606
commit
5b2a399392
8 changed files with 115 additions and 35 deletions
|
@ -510,6 +510,10 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable MessageRecord getLastVisibleMessageRecord(int position) {
|
||||||
|
return getItem(position - ((hasFooter() && position == getItemCount() - 1) ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
static class ConversationViewHolder extends RecyclerView.ViewHolder {
|
static class ConversationViewHolder extends RecyclerView.ViewHolder {
|
||||||
public <V extends View & BindableConversationItem> ConversationViewHolder(final @NonNull V itemView) {
|
public <V extends View & BindableConversationItem> ConversationViewHolder(final @NonNull V itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
|
|
@ -7,6 +7,7 @@ final class ConversationData {
|
||||||
private final long threadId;
|
private final long threadId;
|
||||||
private final long lastSeen;
|
private final long lastSeen;
|
||||||
private final int lastSeenPosition;
|
private final int lastSeenPosition;
|
||||||
|
private final int lastScrolledPosition;
|
||||||
private final boolean hasSent;
|
private final boolean hasSent;
|
||||||
private final boolean isMessageRequestAccepted;
|
private final boolean isMessageRequestAccepted;
|
||||||
private final boolean hasPreMessageRequestMessages;
|
private final boolean hasPreMessageRequestMessages;
|
||||||
|
@ -15,6 +16,7 @@ final class ConversationData {
|
||||||
ConversationData(long threadId,
|
ConversationData(long threadId,
|
||||||
long lastSeen,
|
long lastSeen,
|
||||||
int lastSeenPosition,
|
int lastSeenPosition,
|
||||||
|
int lastScrolledPosition,
|
||||||
boolean hasSent,
|
boolean hasSent,
|
||||||
boolean isMessageRequestAccepted,
|
boolean isMessageRequestAccepted,
|
||||||
boolean hasPreMessageRequestMessages,
|
boolean hasPreMessageRequestMessages,
|
||||||
|
@ -23,6 +25,7 @@ final class ConversationData {
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
this.lastSeen = lastSeen;
|
this.lastSeen = lastSeen;
|
||||||
this.lastSeenPosition = lastSeenPosition;
|
this.lastSeenPosition = lastSeenPosition;
|
||||||
|
this.lastScrolledPosition = lastScrolledPosition;
|
||||||
this.hasSent = hasSent;
|
this.hasSent = hasSent;
|
||||||
this.isMessageRequestAccepted = isMessageRequestAccepted;
|
this.isMessageRequestAccepted = isMessageRequestAccepted;
|
||||||
this.hasPreMessageRequestMessages = hasPreMessageRequestMessages;
|
this.hasPreMessageRequestMessages = hasPreMessageRequestMessages;
|
||||||
|
@ -41,6 +44,10 @@ final class ConversationData {
|
||||||
return lastSeenPosition;
|
return lastSeenPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getLastScrolledPosition() {
|
||||||
|
return lastScrolledPosition;
|
||||||
|
}
|
||||||
|
|
||||||
boolean hasSent() {
|
boolean hasSent() {
|
||||||
return hasSent;
|
return hasSent;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +64,10 @@ final class ConversationData {
|
||||||
return jumpToPosition >= 0;
|
return jumpToPosition >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean shouldScrollToLastSeen() {
|
||||||
|
return lastSeenPosition > 0;
|
||||||
|
}
|
||||||
|
|
||||||
int getJumpToPosition() {
|
int getJumpToPosition() {
|
||||||
return jumpToPosition;
|
return jumpToPosition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,6 +284,23 @@ public class ConversationFragment extends Fragment {
|
||||||
initializeTypingObserver();
|
initializeTypingObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
int lastVisiblePosition = getListLayoutManager().findLastVisibleItemPosition();
|
||||||
|
int firstVisiblePosition = getListLayoutManager().findFirstCompletelyVisibleItemPosition();
|
||||||
|
|
||||||
|
final long lastVisibleMessageTimestamp;
|
||||||
|
if (firstVisiblePosition != 0 && lastVisiblePosition != RecyclerView.NO_POSITION) {
|
||||||
|
MessageRecord message = getListAdapter().getLastVisibleMessageRecord(lastVisiblePosition);
|
||||||
|
|
||||||
|
lastVisibleMessageTimestamp = message != null ? message.getDateReceived() : 0;
|
||||||
|
} else {
|
||||||
|
lastVisibleMessageTimestamp = 0;
|
||||||
|
}
|
||||||
|
SignalExecutors.BOUNDED.submit(() -> DatabaseFactory.getThreadDatabase(requireContext()).setLastScrolled(threadId, lastVisibleMessageTimestamp));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
@ -312,7 +329,7 @@ public class ConversationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
int position = getListAdapter().getAdapterPositionForMessagePosition(conversationViewModel.getLastSeenPosition());
|
int position = getListAdapter().getAdapterPositionForMessagePosition(conversationViewModel.getLastSeenPosition());
|
||||||
scrollToLastSeenPosition(position);
|
scrollToPosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMessageRequestViewModel() {
|
private void initializeMessageRequestViewModel() {
|
||||||
|
@ -887,11 +904,12 @@ public class ConversationFragment extends Fragment {
|
||||||
listener.onCursorChanged();
|
listener.onCursorChanged();
|
||||||
|
|
||||||
int lastSeenPosition = adapter.getAdapterPositionForMessagePosition(conversation.getLastSeenPosition());
|
int lastSeenPosition = adapter.getAdapterPositionForMessagePosition(conversation.getLastSeenPosition());
|
||||||
|
int lastScrolledPosition = adapter.getAdapterPositionForMessagePosition(conversation.getLastScrolledPosition());
|
||||||
|
|
||||||
if (conversation.shouldJumpToMessage()) {
|
if (conversation.shouldJumpToMessage()) {
|
||||||
scrollToStartingPosition(conversation.getJumpToPosition());
|
scrollToStartingPosition(conversation.getJumpToPosition());
|
||||||
} else if (conversation.isMessageRequestAccepted()) {
|
} else if (conversation.isMessageRequestAccepted()) {
|
||||||
scrollToLastSeenPosition(lastSeenPosition);
|
scrollToPosition(conversation.shouldScrollToLastSeen() ? lastSeenPosition : lastScrolledPosition);
|
||||||
} else if (FeatureFlags.messageRequests()) {
|
} else if (FeatureFlags.messageRequests()) {
|
||||||
list.post(() -> getListLayoutManager().scrollToPosition(adapter.getItemCount() - 1));
|
list.post(() -> getListLayoutManager().scrollToPosition(adapter.getItemCount() - 1));
|
||||||
}
|
}
|
||||||
|
@ -904,9 +922,9 @@ public class ConversationFragment extends Fragment {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scrollToLastSeenPosition(int lastSeenPosition) {
|
private void scrollToPosition(int position) {
|
||||||
if (lastSeenPosition > 0) {
|
if (position > 0) {
|
||||||
list.post(() -> getListLayoutManager().scrollToPositionWithOffset(lastSeenPosition, list.getHeight()));
|
list.post(() -> getListLayoutManager().scrollToPositionWithOffset(position, list.getHeight()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
@ -35,23 +35,29 @@ class ConversationRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull ConversationData getConversationDataInternal(long threadId, int jumpToPosition) {
|
private @NonNull ConversationData getConversationDataInternal(long threadId, int jumpToPosition) {
|
||||||
Pair<Long, Boolean> lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId);
|
ThreadDatabase.ConversationMetadata metadata = DatabaseFactory.getThreadDatabase(context).getConversationMetadata(threadId);
|
||||||
|
|
||||||
long lastSeen = lastSeenAndHasSent.first();
|
long lastSeen = metadata.getLastSeen();
|
||||||
boolean hasSent = lastSeenAndHasSent.second();
|
boolean hasSent = metadata.hasSent();
|
||||||
int lastSeenPosition = 0;
|
int lastSeenPosition = 0;
|
||||||
|
long lastScrolled = metadata.getLastScrolled();
|
||||||
|
int lastScrolledPosition = 0;
|
||||||
|
|
||||||
boolean isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId);
|
boolean isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId);
|
||||||
boolean hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId);
|
boolean hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId);
|
||||||
|
|
||||||
if (lastSeen > 0) {
|
if (lastSeen > 0) {
|
||||||
lastSeenPosition = DatabaseFactory.getMmsSmsDatabase(context).getMessagePositionForLastSeen(threadId, lastSeen);
|
lastSeenPosition = DatabaseFactory.getMmsSmsDatabase(context).getMessagePositionOnOrAfterTimestamp(threadId, lastSeen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastSeenPosition <= 0) {
|
if (lastSeenPosition <= 0) {
|
||||||
lastSeen = 0;
|
lastSeen = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ConversationData(threadId, lastSeen, lastSeenPosition, hasSent, isMessageRequestAccepted, hasPreMessageRequestMessages, jumpToPosition);
|
if (lastSeen == 0 && lastScrolled > 0) {
|
||||||
|
lastScrolledPosition = DatabaseFactory.getMmsSmsDatabase(context).getMessagePositionOnOrAfterTimestamp(threadId, lastScrolled);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConversationData(threadId, lastSeen, lastSeenPosition, lastScrolledPosition, hasSent, isMessageRequestAccepted, hasPreMessageRequestMessages, jumpToPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,11 +70,13 @@ class ConversationViewModel extends ViewModel {
|
||||||
final int startPosition;
|
final int startPosition;
|
||||||
if (data.shouldJumpToMessage()) {
|
if (data.shouldJumpToMessage()) {
|
||||||
startPosition = data.getJumpToPosition();
|
startPosition = data.getJumpToPosition();
|
||||||
} else {
|
} else if (data.shouldScrollToLastSeen()) {
|
||||||
startPosition = data.getLastSeenPosition();
|
startPosition = data.getLastSeenPosition();
|
||||||
|
} else {
|
||||||
|
startPosition = data.getLastScrolledPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Starting at position " + startPosition + " :: " + data.getJumpToPosition() + " :: " + data.getLastSeenPosition());
|
Log.d(TAG, "Starting at position startPosition: " + startPosition + " jumpToPosition: " + jumpToPosition + " lastSeenPosition: " + data.getLastSeenPosition() + " lastScrolledPosition: " + data.getLastScrolledPosition());
|
||||||
|
|
||||||
return Transformations.map(new LivePagedListBuilder<>(factory, config).setFetchExecutor(ConversationDataSource.EXECUTOR)
|
return Transformations.map(new LivePagedListBuilder<>(factory, config).setFetchExecutor(ConversationDataSource.EXECUTOR)
|
||||||
.setInitialLoadKey(Math.max(startPosition, 0))
|
.setInitialLoadKey(Math.max(startPosition, 0))
|
||||||
|
|
|
@ -124,9 +124,9 @@ public class MmsSmsDatabase extends Database {
|
||||||
return new Pair<>(id, latestQuit);
|
return new Pair<>(id, latestQuit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMessagePositionForLastSeen(long threadId, long lastSeen) {
|
public int getMessagePositionOnOrAfterTimestamp(long threadId, long timestamp) {
|
||||||
String[] projection = new String[] { "COUNT(*)" };
|
String[] projection = new String[] { "COUNT(*)" };
|
||||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " > " + lastSeen;
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " >= " + timestamp;
|
||||||
|
|
||||||
try (Cursor cursor = queryTables(projection, selection, null, null)) {
|
try (Cursor cursor = queryTables(projection, selection, null, null)) {
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
|
|
|
@ -90,6 +90,7 @@ public class ThreadDatabase extends Database {
|
||||||
public static final String EXPIRES_IN = "expires_in";
|
public static final String EXPIRES_IN = "expires_in";
|
||||||
public static final String LAST_SEEN = "last_seen";
|
public static final String LAST_SEEN = "last_seen";
|
||||||
public static final String HAS_SENT = "has_sent";
|
public static final String HAS_SENT = "has_sent";
|
||||||
|
private static final String LAST_SCROLLED = "last_scrolled";
|
||||||
|
|
||||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
|
||||||
DATE + " INTEGER DEFAULT 0, " +
|
DATE + " INTEGER DEFAULT 0, " +
|
||||||
|
@ -111,7 +112,8 @@ public class ThreadDatabase extends Database {
|
||||||
LAST_SEEN + " INTEGER DEFAULT 0, " +
|
LAST_SEEN + " INTEGER DEFAULT 0, " +
|
||||||
HAS_SENT + " INTEGER DEFAULT 0, " +
|
HAS_SENT + " INTEGER DEFAULT 0, " +
|
||||||
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " +
|
READ_RECEIPT_COUNT + " INTEGER DEFAULT 0, " +
|
||||||
UNREAD_COUNT + " INTEGER DEFAULT 0);";
|
UNREAD_COUNT + " INTEGER DEFAULT 0, " +
|
||||||
|
LAST_SCROLLED + " INTEGER DEFAULT 0);";
|
||||||
|
|
||||||
public static final String[] CREATE_INDEXS = {
|
public static final String[] CREATE_INDEXS = {
|
||||||
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ");",
|
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_ID + ");",
|
||||||
|
@ -120,7 +122,7 @@ public class ThreadDatabase extends Database {
|
||||||
|
|
||||||
private static final String[] THREAD_PROJECTION = {
|
private static final String[] THREAD_PROJECTION = {
|
||||||
ID, DATE, MESSAGE_COUNT, RECIPIENT_ID, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
|
ID, DATE, MESSAGE_COUNT, RECIPIENT_ID, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
|
||||||
SNIPPET_URI, SNIPPET_CONTENT_TYPE, SNIPPET_EXTRAS, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT
|
SNIPPET_URI, SNIPPET_CONTENT_TYPE, SNIPPET_EXTRAS, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, LAST_SCROLLED
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final List<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION)
|
private static final List<String> TYPED_THREAD_PROJECTION = Stream.of(THREAD_PROJECTION)
|
||||||
|
@ -618,18 +620,26 @@ public class ThreadDatabase extends Database {
|
||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<Long, Boolean> getLastSeenAndHasSent(long threadId) {
|
public void setLastScrolled(long threadId, long lastScrolledTimestamp) {
|
||||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
|
ContentValues contentValues = new ContentValues(1);
|
||||||
|
|
||||||
try {
|
contentValues.put(LAST_SCROLLED, lastScrolledTimestamp);
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
return new Pair<>(cursor.getLong(0), cursor.getLong(1) == 1);
|
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Pair<>(-1L, false);
|
public ConversationMetadata getConversationMetadata(long threadId) {
|
||||||
} finally {
|
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||||
if (cursor != null) cursor.close();
|
|
||||||
|
try (Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT, LAST_SCROLLED}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null)) {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
return new ConversationMetadata(cursor.getLong(cursor.getColumnIndexOrThrow(LAST_SEEN)),
|
||||||
|
cursor.getLong(cursor.getColumnIndexOrThrow(HAS_SENT)) == 1,
|
||||||
|
cursor.getLong(cursor.getColumnIndexOrThrow(LAST_SCROLLED)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConversationMetadata(-1L, false, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1060,4 +1070,28 @@ public class ThreadDatabase extends Database {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ConversationMetadata {
|
||||||
|
private final long lastSeen;
|
||||||
|
private final boolean hasSent;
|
||||||
|
private final long lastScrolled;
|
||||||
|
|
||||||
|
public ConversationMetadata(long lastSeen, boolean hasSent, long lastScrolled) {
|
||||||
|
this.lastSeen = lastSeen;
|
||||||
|
this.hasSent = hasSent;
|
||||||
|
this.lastScrolled = lastScrolled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastSeen() {
|
||||||
|
return lastSeen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSent() {
|
||||||
|
return hasSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastScrolled() {
|
||||||
|
return lastScrolled;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,8 +134,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||||
private static final int SERVER_TIMESTAMP = 59;
|
private static final int SERVER_TIMESTAMP = 59;
|
||||||
private static final int REMOTE_DELETE = 60;
|
private static final int REMOTE_DELETE = 60;
|
||||||
private static final int COLOR_MIGRATION = 61;
|
private static final int COLOR_MIGRATION = 61;
|
||||||
|
private static final int LAST_SCROLLED = 62;
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 61;
|
private static final int DATABASE_VERSION = 62;
|
||||||
private static final String DATABASE_NAME = "signal.db";
|
private static final String DATABASE_NAME = "signal.db";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
@ -906,6 +907,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < LAST_SCROLLED) {
|
||||||
|
db.execSQL("ALTER TABLE thread ADD COLUMN last_scrolled INTEGER DEFAULT 0");
|
||||||
|
}
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
|
Loading…
Add table
Reference in a new issue