mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 07:48:34 +00:00
Transition conversation loading from a Loader to a Repository.
This commit is contained in:
parent
2b65916344
commit
e1a90bcb00
@ -0,0 +1,82 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public final class ConversationData {
|
||||||
|
private final Cursor cursor;
|
||||||
|
private final int offset;
|
||||||
|
private final int limit;
|
||||||
|
private final long lastSeen;
|
||||||
|
private final int previousOffset;
|
||||||
|
private final boolean firstLoad;
|
||||||
|
private final boolean hasSent;
|
||||||
|
private final boolean isMessageRequestAccepted;
|
||||||
|
private final boolean hasPreMessageRequestMessages;
|
||||||
|
|
||||||
|
public ConversationData(Cursor cursor,
|
||||||
|
int offset,
|
||||||
|
int limit,
|
||||||
|
long lastSeen,
|
||||||
|
int previousOffset,
|
||||||
|
boolean firstLoad,
|
||||||
|
boolean hasSent,
|
||||||
|
boolean isMessageRequestAccepted,
|
||||||
|
boolean hasPreMessageRequestMessages)
|
||||||
|
{
|
||||||
|
this.cursor = cursor;
|
||||||
|
this.offset = offset;
|
||||||
|
this.limit = limit;
|
||||||
|
this.lastSeen = lastSeen;
|
||||||
|
this.previousOffset = previousOffset;
|
||||||
|
this.firstLoad = firstLoad;
|
||||||
|
this.hasSent = hasSent;
|
||||||
|
this.isMessageRequestAccepted = isMessageRequestAccepted;
|
||||||
|
this.hasPreMessageRequestMessages = hasPreMessageRequestMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull Cursor getCursor() {
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasLimit() {
|
||||||
|
return limit > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLimit() {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOffset() {
|
||||||
|
return offset > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreviousOffset() {
|
||||||
|
return previousOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastSeen() {
|
||||||
|
return lastSeen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFirstLoad() {
|
||||||
|
return firstLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSent() {
|
||||||
|
return hasSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMessageRequestAccepted() {
|
||||||
|
return isMessageRequestAccepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPreMessageRequestMessages() {
|
||||||
|
return hasPreMessageRequestMessages;
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,7 @@ import androidx.core.app.ActivityCompat;
|
|||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
@ -81,7 +82,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|||||||
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
|
|
||||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
@ -135,9 +135,7 @@ import java.util.Locale;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public class ConversationFragment extends Fragment
|
public class ConversationFragment extends Fragment {
|
||||||
implements LoaderManager.LoaderCallbacks<Cursor>
|
|
||||||
{
|
|
||||||
private static final String TAG = ConversationFragment.class.getSimpleName();
|
private static final String TAG = ConversationFragment.class.getSimpleName();
|
||||||
private static final String KEY_LIMIT = "limit";
|
private static final String KEY_LIMIT = "limit";
|
||||||
|
|
||||||
@ -152,11 +150,6 @@ public class ConversationFragment extends Fragment
|
|||||||
|
|
||||||
private LiveRecipient recipient;
|
private LiveRecipient recipient;
|
||||||
private long threadId;
|
private long threadId;
|
||||||
private long lastSeen;
|
|
||||||
private int startingPosition;
|
|
||||||
private int previousOffset;
|
|
||||||
private int activeOffset;
|
|
||||||
private boolean firstLoad;
|
|
||||||
private boolean isReacting;
|
private boolean isReacting;
|
||||||
private ActionMode actionMode;
|
private ActionMode actionMode;
|
||||||
private Locale locale;
|
private Locale locale;
|
||||||
@ -172,6 +165,7 @@ public class ConversationFragment extends Fragment
|
|||||||
private ConversationBannerView conversationBanner;
|
private ConversationBannerView conversationBanner;
|
||||||
private ConversationBannerView emptyConversationBanner;
|
private ConversationBannerView emptyConversationBanner;
|
||||||
private MessageRequestViewModel messageRequestViewModel;
|
private MessageRequestViewModel messageRequestViewModel;
|
||||||
|
private ConversationViewModel conversationViewModel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
@ -214,6 +208,9 @@ public class ConversationFragment extends Fragment
|
|||||||
|
|
||||||
setupListLayoutListeners();
|
setupListLayoutListeners();
|
||||||
|
|
||||||
|
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
|
||||||
|
conversationViewModel.getConversation().observe(this, this::presentConversation);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,16 +292,16 @@ public class ConversationFragment extends Fragment
|
|||||||
initializeListAdapter();
|
initializeListAdapter();
|
||||||
|
|
||||||
if (threadId == -1) {
|
if (threadId == -1) {
|
||||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
|
conversationViewModel.refreshConversation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadList() {
|
public void reloadList() {
|
||||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
|
conversationViewModel.refreshConversation();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void moveToLastSeen() {
|
public void moveToLastSeen() {
|
||||||
if (lastSeen <= 0) {
|
if (conversationViewModel.getLastSeen() <= 0) {
|
||||||
Log.i(TAG, "No need to move to last seen.");
|
Log.i(TAG, "No need to move to last seen.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -314,7 +311,7 @@ public class ConversationFragment extends Fragment
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int position = getListAdapter().findLastSeenPosition(lastSeen);
|
int position = getListAdapter().findLastSeenPosition(conversationViewModel.getLastSeen());
|
||||||
scrollToLastSeenPosition(position);
|
scrollToLastSeenPosition(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,13 +400,17 @@ public class ConversationFragment extends Fragment
|
|||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
long oldThreadId = threadId;
|
long oldThreadId = threadId;
|
||||||
|
|
||||||
|
long lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1);
|
||||||
|
int startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
|
||||||
|
int limit = getArguments() != null ? getArguments().getInt(KEY_LIMIT, PARTIAL_CONVERSATION_LIMIT) : PARTIAL_CONVERSATION_LIMIT;
|
||||||
|
|
||||||
this.recipient = Recipient.live(getActivity().getIntent().getParcelableExtra(ConversationActivity.RECIPIENT_EXTRA));
|
this.recipient = Recipient.live(getActivity().getIntent().getParcelableExtra(ConversationActivity.RECIPIENT_EXTRA));
|
||||||
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
|
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
|
||||||
this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1);
|
|
||||||
this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
|
|
||||||
this.firstLoad = true;
|
|
||||||
this.unknownSenderView = new UnknownSenderView(getActivity(), recipient.get(), threadId);
|
this.unknownSenderView = new UnknownSenderView(getActivity(), recipient.get(), threadId);
|
||||||
|
|
||||||
|
|
||||||
|
conversationViewModel.onConversationDataAvailable(recipient.get(), threadId, lastSeen, startingPosition, limit);
|
||||||
|
|
||||||
OnScrollListener scrollListener = new ConversationScrollListener(getActivity());
|
OnScrollListener scrollListener = new ConversationScrollListener(getActivity());
|
||||||
list.addOnScrollListener(scrollListener);
|
list.addOnScrollListener(scrollListener);
|
||||||
|
|
||||||
@ -425,8 +426,7 @@ public class ConversationFragment extends Fragment
|
|||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false));
|
list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false));
|
||||||
|
|
||||||
setLastSeen(lastSeen);
|
setLastSeen(conversationViewModel.getLastSeen());
|
||||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
|
|
||||||
|
|
||||||
emptyConversationBanner.setVisibility(View.GONE);
|
emptyConversationBanner.setVisibility(View.GONE);
|
||||||
} else if (FeatureFlags.messageRequests() && threadId == -1) {
|
} else if (FeatureFlags.messageRequests() && threadId == -1) {
|
||||||
@ -436,9 +436,7 @@ public class ConversationFragment extends Fragment
|
|||||||
|
|
||||||
private void initializeLoadMoreView(ViewSwitcher loadMoreView) {
|
private void initializeLoadMoreView(ViewSwitcher loadMoreView) {
|
||||||
loadMoreView.setOnClickListener(v -> {
|
loadMoreView.setOnClickListener(v -> {
|
||||||
Bundle args = new Bundle();
|
conversationViewModel.onLoadMoreClicked();
|
||||||
args.putInt(KEY_LIMIT, 0);
|
|
||||||
getLoaderManager().restartLoader(0, args, ConversationFragment.this);
|
|
||||||
loadMoreView.showNext();
|
loadMoreView.showNext();
|
||||||
loadMoreView.setOnClickListener(null);
|
loadMoreView.setOnClickListener(null);
|
||||||
});
|
});
|
||||||
@ -565,7 +563,8 @@ public class ConversationFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setLastSeen(long lastSeen) {
|
public void setLastSeen(long lastSeen) {
|
||||||
this.lastSeen = lastSeen;
|
conversationViewModel.onLastSeenChanged(lastSeen);
|
||||||
|
|
||||||
if (lastSeenDecoration != null) {
|
if (lastSeenDecoration != null) {
|
||||||
list.removeItemDecoration(lastSeenDecoration);
|
list.removeItemDecoration(lastSeenDecoration);
|
||||||
}
|
}
|
||||||
@ -827,109 +826,12 @@ public class ConversationFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
|
||||||
Log.i(TAG, "onCreateLoader");
|
|
||||||
|
|
||||||
int limit = args.getInt(KEY_LIMIT, PARTIAL_CONVERSATION_LIMIT);
|
|
||||||
int offset = 0;
|
|
||||||
if (limit != 0 && startingPosition >= limit) {
|
|
||||||
offset = Math.max(startingPosition - (limit / 2) + 1, 0);
|
|
||||||
startingPosition -= offset - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ConversationLoader(getActivity(), threadId, offset, limit, lastSeen);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(@NonNull Loader<Cursor> cursorLoader, Cursor cursor) {
|
|
||||||
int count = cursor.getCount();
|
|
||||||
ConversationLoader loader = (ConversationLoader) cursorLoader;
|
|
||||||
|
|
||||||
ConversationAdapter adapter = getListAdapter();
|
|
||||||
if (adapter == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) {
|
|
||||||
adapter.setFooterView(topLoadMoreView);
|
|
||||||
} else if (FeatureFlags.messageRequests()) {
|
|
||||||
adapter.setFooterView(conversationBanner);
|
|
||||||
} else {
|
|
||||||
adapter.setFooterView(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastSeen == -1) {
|
|
||||||
setLastSeen(loader.getLastSeen());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FeatureFlags.messageRequests() && !loader.hasPreMessageRequestMessages()) {
|
|
||||||
clearHeaderIfNotTyping(adapter);
|
|
||||||
} else {
|
|
||||||
if (!loader.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
|
|
||||||
adapter.setHeaderView(unknownSenderView);
|
|
||||||
} else {
|
|
||||||
clearHeaderIfNotTyping(adapter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loader.hasOffset()) {
|
|
||||||
adapter.setHeaderView(bottomLoadMoreView);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstLoad || loader.hasOffset()) {
|
|
||||||
previousOffset = loader.getOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
activeOffset = loader.getOffset();
|
|
||||||
adapter.changeCursor(cursor);
|
|
||||||
listener.onCursorChanged();
|
|
||||||
|
|
||||||
int lastSeenPosition = adapter.findLastSeenPosition(lastSeen);
|
|
||||||
|
|
||||||
if (isTypingIndicatorShowing()) {
|
|
||||||
lastSeenPosition = Math.max(lastSeenPosition - 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstLoad) {
|
|
||||||
if (startingPosition >= 0) {
|
|
||||||
scrollToStartingPosition(startingPosition);
|
|
||||||
} else if (loader.isMessageRequestAccepted()) {
|
|
||||||
scrollToLastSeenPosition(lastSeenPosition);
|
|
||||||
} else if (FeatureFlags.messageRequests()) {
|
|
||||||
list.post(() -> getListLayoutManager().scrollToPosition(adapter.getItemCount() - 1));
|
|
||||||
}
|
|
||||||
firstLoad = false;
|
|
||||||
} else if (previousOffset > 0) {
|
|
||||||
int scrollPosition = previousOffset + getListLayoutManager().findFirstVisibleItemPosition();
|
|
||||||
scrollPosition = Math.min(scrollPosition, count - 1);
|
|
||||||
|
|
||||||
View firstView = list.getLayoutManager().getChildAt(scrollPosition);
|
|
||||||
int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom());
|
|
||||||
|
|
||||||
getListLayoutManager().scrollToPositionWithOffset(scrollPosition, pixelOffset);
|
|
||||||
previousOffset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastSeenPosition <= 0) {
|
|
||||||
setLastSeen(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearHeaderIfNotTyping(ConversationAdapter adapter) {
|
private void clearHeaderIfNotTyping(ConversationAdapter adapter) {
|
||||||
if (adapter.getHeaderView() != typingView) {
|
if (adapter.getHeaderView() != typingView) {
|
||||||
adapter.setHeaderView(null);
|
adapter.setHeaderView(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(@NonNull Loader<Cursor> arg0) {
|
|
||||||
if (list.getAdapter() != null) {
|
|
||||||
getListAdapter().changeCursor(null);
|
|
||||||
listener.onCursorChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long stageOutgoingMessage(OutgoingMediaMessage message) {
|
public long stageOutgoingMessage(OutgoingMediaMessage message) {
|
||||||
MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent();
|
MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent();
|
||||||
|
|
||||||
@ -960,6 +862,73 @@ public class ConversationFragment extends Fragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void presentConversation(@NonNull ConversationData conversation) {
|
||||||
|
Cursor cursor = conversation.getCursor();
|
||||||
|
int count = cursor.getCount();
|
||||||
|
|
||||||
|
ConversationAdapter adapter = getListAdapter();
|
||||||
|
if (adapter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && conversation.hasLimit()) {
|
||||||
|
adapter.setFooterView(topLoadMoreView);
|
||||||
|
} else if (FeatureFlags.messageRequests()) {
|
||||||
|
adapter.setFooterView(conversationBanner);
|
||||||
|
} else {
|
||||||
|
adapter.setFooterView(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversationViewModel.getLastSeen() == -1) {
|
||||||
|
setLastSeen(conversation.getLastSeen());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FeatureFlags.messageRequests() && !conversation.hasPreMessageRequestMessages()) {
|
||||||
|
clearHeaderIfNotTyping(adapter);
|
||||||
|
} else {
|
||||||
|
if (!conversation.hasSent() && !recipient.get().isSystemContact() && !recipient.get().isGroup() && recipient.get().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
|
||||||
|
adapter.setHeaderView(unknownSenderView);
|
||||||
|
} else {
|
||||||
|
clearHeaderIfNotTyping(adapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversation.hasOffset()) {
|
||||||
|
adapter.setHeaderView(bottomLoadMoreView);
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.changeCursor(cursor);
|
||||||
|
listener.onCursorChanged();
|
||||||
|
|
||||||
|
int lastSeenPosition = adapter.findLastSeenPosition(conversationViewModel.getLastSeen());
|
||||||
|
|
||||||
|
if (isTypingIndicatorShowing()) {
|
||||||
|
lastSeenPosition = Math.max(lastSeenPosition - 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversation.isFirstLoad()) {
|
||||||
|
if (conversationViewModel.getStartingPosition() >= 0) {
|
||||||
|
scrollToStartingPosition(conversationViewModel.getStartingPosition());
|
||||||
|
} else if (conversation.isMessageRequestAccepted()) {
|
||||||
|
scrollToLastSeenPosition(lastSeenPosition);
|
||||||
|
} else if (FeatureFlags.messageRequests()) {
|
||||||
|
list.post(() -> getListLayoutManager().scrollToPosition(adapter.getItemCount() - 1));
|
||||||
|
}
|
||||||
|
} else if (conversation.getPreviousOffset() > 0) {
|
||||||
|
int scrollPosition = conversation.getPreviousOffset() + getListLayoutManager().findFirstVisibleItemPosition();
|
||||||
|
scrollPosition = Math.min(scrollPosition, count - 1);
|
||||||
|
|
||||||
|
View firstView = list.getLayoutManager().getChildAt(scrollPosition);
|
||||||
|
int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom());
|
||||||
|
|
||||||
|
getListLayoutManager().scrollToPositionWithOffset(scrollPosition, pixelOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSeenPosition <= 0) {
|
||||||
|
setLastSeen(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void scrollToStartingPosition(final int startingPosition) {
|
private void scrollToStartingPosition(final int startingPosition) {
|
||||||
list.post(() -> {
|
list.post(() -> {
|
||||||
list.getLayoutManager().scrollToPosition(startingPosition);
|
list.getLayoutManager().scrollToPosition(startingPosition);
|
||||||
@ -1004,6 +973,8 @@ public class ConversationFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void moveToMessagePosition(int position, @Nullable Runnable onMessageNotFound) {
|
private void moveToMessagePosition(int position, @Nullable Runnable onMessageNotFound) {
|
||||||
|
int activeOffset = conversationViewModel.getActiveOffset();
|
||||||
|
|
||||||
Log.d(TAG, "Moving to message position: " + position + " activeOffset: " + activeOffset + " cursorCount: " + getListAdapter().getCursorCount());
|
Log.d(TAG, "Moving to message position: " + position + " activeOffset: " + activeOffset + " cursorCount: " + getListAdapter().getCursorCount());
|
||||||
|
|
||||||
if (position >= activeOffset && position >= 0 && position < getListAdapter().getCursorCount()) {
|
if (position >= activeOffset && position >= 0 && position < getListAdapter().getCursorCount()) {
|
||||||
@ -1018,9 +989,7 @@ public class ConversationFragment extends Fragment
|
|||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "Message was outside of the loaded range. Need to restart the loader.");
|
Log.i(TAG, "Message was outside of the loaded range. Need to restart the loader.");
|
||||||
|
|
||||||
firstLoad = true;
|
conversationViewModel.onMoveJumpToMessageOutOfRange(position);
|
||||||
startingPosition = position;
|
|
||||||
getLoaderManager().restartLoader(0, Bundle.EMPTY, ConversationFragment.this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
public class ConversationRepository {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Executor executor;
|
||||||
|
|
||||||
|
public ConversationRepository() {
|
||||||
|
this.context = ApplicationDependencies.getApplication();
|
||||||
|
this.executor = SignalExecutors.BOUNDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getConversationData(long threadId,
|
||||||
|
int offset,
|
||||||
|
int limit,
|
||||||
|
long lastSeen,
|
||||||
|
int previousOffset,
|
||||||
|
boolean firstLoad,
|
||||||
|
@NonNull Callback<ConversationData> callback)
|
||||||
|
{
|
||||||
|
executor.execute(() -> callback.onComplete(getConversationDataInternal(threadId, offset, limit, lastSeen, previousOffset, firstLoad)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull ConversationData getConversationDataInternal(long threadId, int offset, int limit, long lastSeen, int previousOffset, boolean firstLoad) {
|
||||||
|
Pair<Long, Boolean> lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId);
|
||||||
|
|
||||||
|
boolean hasSent = lastSeenAndHasSent.second();
|
||||||
|
|
||||||
|
if (lastSeen == -1) {
|
||||||
|
lastSeen = lastSeenAndHasSent.first();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId);
|
||||||
|
boolean hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId);
|
||||||
|
Cursor cursor = DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, offset, limit);
|
||||||
|
|
||||||
|
return new ConversationData(cursor, offset, limit, lastSeen, previousOffset, firstLoad, hasSent, isMessageRequestAccepted, hasPreMessageRequestMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface Callback<E> {
|
||||||
|
void onComplete(@NonNull E result);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,11 @@
|
|||||||
package org.thoughtcrime.securesms.conversation;
|
package org.thoughtcrime.securesms.conversation;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.ContentObservable;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Handler;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
@ -8,32 +13,139 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaRepository;
|
import org.thoughtcrime.securesms.mediasend.MediaRepository;
|
||||||
|
import org.thoughtcrime.securesms.pin.PinState;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class ConversationViewModel extends ViewModel {
|
class ConversationViewModel extends ViewModel {
|
||||||
|
|
||||||
private final Context context;
|
private static final String TAG = Log.tag(ConversationViewModel.class);
|
||||||
private final MediaRepository mediaRepository;
|
|
||||||
private final MutableLiveData<List<Media>> recentMedia;
|
private static final int NO_LIMIT = 0;
|
||||||
|
|
||||||
|
private final Application context;
|
||||||
|
private final MediaRepository mediaRepository;
|
||||||
|
private final ConversationRepository conversationRepository;
|
||||||
|
private final MutableLiveData<List<Media>> recentMedia;
|
||||||
|
private final MutableLiveData<ConversationData> conversation;
|
||||||
|
private final ContentObserver contentObserver;
|
||||||
|
|
||||||
|
private Recipient recipient;
|
||||||
|
private long threadId;
|
||||||
|
private boolean firstLoad;
|
||||||
|
private int requestedLimit;
|
||||||
|
private long lastSeen;
|
||||||
|
private int startingPosition;
|
||||||
|
private int previousOffset;
|
||||||
|
private boolean contentObserverRegistered;
|
||||||
|
|
||||||
private ConversationViewModel() {
|
private ConversationViewModel() {
|
||||||
this.context = ApplicationDependencies.getApplication();
|
this.context = ApplicationDependencies.getApplication();
|
||||||
this.mediaRepository = new MediaRepository();
|
this.mediaRepository = new MediaRepository();
|
||||||
this.recentMedia = new MutableLiveData<>();
|
this.conversationRepository = new ConversationRepository();
|
||||||
|
this.recentMedia = new MutableLiveData<>();
|
||||||
|
this.conversation = new MutableLiveData<>();
|
||||||
|
this.contentObserver = new ContentObserver(new Handler()) {
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
ConversationData data = conversation.getValue();
|
||||||
|
if (data != null) {
|
||||||
|
conversationRepository.getConversationData(threadId, data.getOffset(), data.getLimit(), data.getLastSeen(), data.getPreviousOffset(), data.isFirstLoad(), conversation::postValue);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Got a content change, but have no previous data?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAttachmentKeyboardOpen() {
|
void onAttachmentKeyboardOpen() {
|
||||||
mediaRepository.getMediaInBucket(context, Media.ALL_MEDIA_BUCKET_ID, recentMedia::postValue);
|
mediaRepository.getMediaInBucket(context, Media.ALL_MEDIA_BUCKET_ID, recentMedia::postValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onConversationDataAvailable(Recipient recipient, long threadId, long lastSeen, int startingPosition, int limit) {
|
||||||
|
this.recipient = recipient;
|
||||||
|
this.threadId = threadId;
|
||||||
|
this.lastSeen = lastSeen;
|
||||||
|
this.startingPosition = startingPosition;
|
||||||
|
this.requestedLimit = limit;
|
||||||
|
this.firstLoad = true;
|
||||||
|
|
||||||
|
if (!contentObserverRegistered) {
|
||||||
|
context.getContentResolver().registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(threadId), true, contentObserver);
|
||||||
|
contentObserverRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshConversation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshConversation() {
|
||||||
|
int limit = requestedLimit;
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
if (requestedLimit != NO_LIMIT && startingPosition >= requestedLimit) {
|
||||||
|
offset = Math.max(startingPosition - (requestedLimit / 2) + 1, 0);
|
||||||
|
startingPosition -= offset - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
conversationRepository.getConversationData(threadId, offset, limit, lastSeen, previousOffset, firstLoad, conversation::postValue);
|
||||||
|
|
||||||
|
if (firstLoad) {
|
||||||
|
firstLoad = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onLoadMoreClicked() {
|
||||||
|
requestedLimit = 0;
|
||||||
|
refreshConversation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMoveJumpToMessageOutOfRange(int startingPosition) {
|
||||||
|
this.firstLoad = true;
|
||||||
|
this.startingPosition = startingPosition;
|
||||||
|
|
||||||
|
refreshConversation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onLastSeenChanged(long lastSeen) {
|
||||||
|
this.lastSeen = lastSeen;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull LiveData<List<Media>> getRecentMedia() {
|
@NonNull LiveData<List<Media>> getRecentMedia() {
|
||||||
return recentMedia;
|
return recentMedia;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull LiveData<ConversationData> getConversation() {
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getLastSeen() {
|
||||||
|
return lastSeen;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStartingPosition() {
|
||||||
|
return startingPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getActiveOffset() {
|
||||||
|
ConversationData data = conversation.getValue();
|
||||||
|
return data != null ? data.getOffset() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
context.getContentResolver().unregisterContentObserver(contentObserver);
|
||||||
|
contentObserverRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
static class Factory extends ViewModelProvider.NewInstanceFactory {
|
||||||
@Override
|
@Override
|
||||||
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
public @NonNull<T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.database.loaders;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
|
|
||||||
import org.whispersystems.libsignal.util.Pair;
|
|
||||||
|
|
||||||
public class ConversationLoader extends AbstractCursorLoader {
|
|
||||||
private final long threadId;
|
|
||||||
private int offset;
|
|
||||||
private int limit;
|
|
||||||
private long lastSeen;
|
|
||||||
private boolean hasSent;
|
|
||||||
private boolean isMessageRequestAccepted;
|
|
||||||
private boolean hasPreMessageRequestMessages;
|
|
||||||
|
|
||||||
public ConversationLoader(Context context, long threadId, int offset, int limit, long lastSeen) {
|
|
||||||
super(context);
|
|
||||||
this.threadId = threadId;
|
|
||||||
this.offset = offset;
|
|
||||||
this.limit = limit;
|
|
||||||
this.lastSeen = lastSeen;
|
|
||||||
this.hasSent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasLimit() {
|
|
||||||
return limit > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasOffset() {
|
|
||||||
return offset > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getOffset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLastSeen() {
|
|
||||||
return lastSeen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasSent() {
|
|
||||||
return hasSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMessageRequestAccepted() {
|
|
||||||
return isMessageRequestAccepted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPreMessageRequestMessages() {
|
|
||||||
return hasPreMessageRequestMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor getCursor() {
|
|
||||||
Pair<Long, Boolean> lastSeenAndHasSent = DatabaseFactory.getThreadDatabase(context).getLastSeenAndHasSent(threadId);
|
|
||||||
|
|
||||||
this.hasSent = lastSeenAndHasSent.second();
|
|
||||||
|
|
||||||
if (lastSeen == -1) {
|
|
||||||
this.lastSeen = lastSeenAndHasSent.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId);
|
|
||||||
this.hasPreMessageRequestMessages = RecipientUtil.isPreMessageRequestThread(context, threadId);
|
|
||||||
|
|
||||||
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId, offset, limit);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user