Skip to content

Commit

Permalink
Optimize open chat
Browse files Browse the repository at this point in the history
  • Loading branch information
Tougee committed Jan 15, 2021
1 parent 0e25f04 commit c972184
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 209 deletions.
338 changes: 181 additions & 157 deletions app/src/main/java/one/mixin/android/db/MessageProvider.kt

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions app/src/main/java/one/mixin/android/extension/LiveDataExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package one.mixin.android.extension

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
observe(
lifecycleOwner,
object : Observer<T> {
override fun onChanged(t: T?) {
observer.onChanged(t)
removeObserver(this)
}
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ internal constructor(
) {

@SuppressLint("RestrictedApi")
fun getMessages(conversationId: String) = MessageProvider.getMessages(conversationId, appDatabase)
fun getMessages(conversationId: String, unreadCount: Int, countable: Boolean) =
MessageProvider.getMessages(conversationId, appDatabase, unreadCount, countable)

fun conversations(circleId: String?): DataSource.Factory<Int, ConversationItem> = if (circleId == null) {
MessageProvider.getConversations(appDatabase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ import androidx.core.view.inputmethod.InputContentInfoCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.paging.PagedList
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
Expand Down Expand Up @@ -105,6 +108,7 @@ import one.mixin.android.extension.isImageSupport
import one.mixin.android.extension.lateOneHours
import one.mixin.android.extension.mainThreadDelayed
import one.mixin.android.extension.networkConnected
import one.mixin.android.extension.observeOnce
import one.mixin.android.extension.openAsUrlOrWeb
import one.mixin.android.extension.openCamera
import one.mixin.android.extension.openMedia
Expand Down Expand Up @@ -1617,62 +1621,76 @@ class ConversationFragment() :
deleteDialog?.show()
}

private fun liveDataMessage(unreadCount: Int, unreadMessageId: String?) {
private var messageLiveData: LiveData<PagedList<MessageItem>>? = null
private lateinit var messageObserver: Observer<PagedList<MessageItem>>

private fun bindLiveData(liveData: LiveData<PagedList<MessageItem>>, countable: Boolean) {
messageLiveData = liveData
if (countable) {
messageLiveData?.observe(viewLifecycleOwner, messageObserver)
} else {
messageLiveData?.observeOnce(viewLifecycleOwner, messageObserver)
}
}

private fun liveDataMessage(unreadCount: Int, unreadMessageId: String?, countable: Boolean) {
var oldCount: Int = -1
chatViewModel.getMessages(conversationId, unreadCount)
.observe(
viewLifecycleOwner,
{ list ->
if (Session.getAccount() == null) return@observe

if (oldCount == -1) {
oldCount = list.size
} else if (!isFirstLoad && !isBottom && list.size > oldCount) {
unreadTipCount += (list.size - oldCount)
oldCount = list.size
} else if (isBottom) {
unreadTipCount = 0
oldCount = list.size
}
chatViewModel.viewModelScope.launch {
chatAdapter.hasBottomView = (
(isBot && list.isEmpty()) ||
(!isGroup && (!list.isEmpty()) && chatViewModel.isSilence(conversationId, sender.userId))
) &&
recipient?.relationship == UserRelationship.STRANGER.name
}
if (isFirstLoad && messageId == null && unreadCount > 0) {
chatAdapter.unreadMsgId = unreadMessageId
} else if (lastReadMessage != null) {
chatViewModel.viewModelScope.launch {
lastReadMessage?.let { id ->
val unreadMsgId = chatViewModel.findUnreadMessageByMessageId(
conversationId,
sender.userId,
id
)
if (unreadMsgId != null) {
chatAdapter.unreadMsgId = unreadMsgId
lastReadMessage = null
}
}
}
}
if (list.size > 0) {
if (isFirstMessage) {
isFirstMessage = false
val start = System.currentTimeMillis()
messageObserver = Observer { list ->
if (Session.getAccount() == null) return@Observer

if (oldCount == -1) {
oldCount = list.size
} else if (!isFirstLoad && !isBottom && list.size > oldCount) {
unreadTipCount += (list.size - oldCount)
oldCount = list.size
} else if (isBottom) {
unreadTipCount = 0
oldCount = list.size
}
chatViewModel.viewModelScope.launch {
chatAdapter.hasBottomView = (
(isBot && list.isEmpty()) ||
(!isGroup && (!list.isEmpty()) && chatViewModel.isSilence(conversationId, sender.userId))
) &&
recipient?.relationship == UserRelationship.STRANGER.name
}
if (isFirstLoad && messageId == null && unreadCount > 0) {
chatAdapter.unreadMsgId = unreadMessageId
} else if (lastReadMessage != null) {
chatViewModel.viewModelScope.launch {
lastReadMessage?.let { id ->
val unreadMsgId = chatViewModel.findUnreadMessageByMessageId(
conversationId,
sender.userId,
id
)
if (unreadMsgId != null) {
chatAdapter.unreadMsgId = unreadMsgId
lastReadMessage = null
}
chatViewModel.markMessageRead(conversationId, sender.userId)
}
chatAdapter.submitList(list)
}
)
}
if (list.size > 0) {
if (isFirstMessage) {
isFirstMessage = false
}
chatViewModel.markMessageRead(conversationId, sender.userId)
}
chatAdapter.submitList(list) {
if (countable) return@submitList

liveDataMessage(unreadCount, unreadMessageId, true)
}
}
bindLiveData(chatViewModel.getMessages(conversationId, unreadCount, countable), countable)
}

private var unreadCount = 0
private fun bindData() {
unreadCount = requireArguments().getInt(UNREAD_COUNT, 0)
liveDataMessage(unreadCount, initialPositionMessageId)
liveDataMessage(unreadCount, initialPositionMessageId, !keyword.isNullOrEmpty())

chatViewModel.getUnreadMentionMessageByConversationId(conversationId).observe(
viewLifecycleOwner,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package one.mixin.android.ui.conversation

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.NotificationManager
import android.content.SharedPreferences
Expand Down Expand Up @@ -107,9 +108,10 @@ internal constructor(
private val messenger: SendMessageHelper
) : ViewModel() {

fun getMessages(id: String, initialLoadKey: Int = 0): LiveData<PagedList<MessageItem>> {
@SuppressLint("RestrictedApi")
fun getMessages(id: String, initialLoadKey: Int = 0, countable: Boolean): LiveData<PagedList<MessageItem>> {
return LivePagedListBuilder(
conversationRepository.getMessages(id),
conversationRepository.getMessages(id, initialLoadKey, countable),
PagedList.Config.Builder()
.setPrefetchDistance(PAGE_SIZE * 2)
.setPageSize(PAGE_SIZE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package one.mixin.android.util.chat;

import android.annotation.SuppressLint;
import android.database.Cursor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.paging.PositionalDataSource;
import androidx.room.InvalidationTracker;
import androidx.room.RoomDatabase;
import androidx.room.RoomSQLiteQuery;
import timber.log.Timber;

import java.util.List;
import java.util.Set;

@SuppressLint("RestrictedApi")
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract class FixedLimitOffsetDataSource<T> extends PositionalDataSource<T> {
private final static int FIXED_LOAD_SIZE = 45;

private final RoomSQLiteQuery mSourceQuery;
private final String mLimitOffsetQuery;
private final int mTotalCount;
private final RoomDatabase mDb;
@SuppressWarnings("FieldCanBeLocal")
private final InvalidationTracker.Observer mObserver;

protected FixedLimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, int unreadCount, String... tables) {
mDb = db;
mTotalCount = unreadCount + FIXED_LOAD_SIZE / 2;
mSourceQuery = query;
mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?";
mObserver = new InvalidationTracker.Observer(tables) {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
db.getInvalidationTracker().addWeakObserver(mObserver);
}

@Override
public boolean isInvalid() {
mDb.getInvalidationTracker().refreshVersionsSync();
return super.isInvalid();
}

@SuppressWarnings("WeakerAccess")
protected abstract List<T> convertRows(Cursor cursor);

private List<T> initialList;

@Override
public void loadInitial(@NonNull LoadInitialParams params,
@NonNull LoadInitialCallback<T> callback) {
final int firstLoadPosition = computeInitialLoadPosition(params, mTotalCount);
final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, mTotalCount);

initialList = loadRange(firstLoadPosition, firstLoadSize);
if (initialList != null) {
try {
callback.onResult(initialList, firstLoadPosition, mTotalCount);
} catch (IllegalArgumentException e) {
// workaround with paging initial load size NOT to be a multiple of page size
Timber.w(e);
try {
callback.onResult(initialList, firstLoadPosition, firstLoadPosition + initialList.size());
} catch (IllegalArgumentException iae) {
// workaround with paging incorrect tiling
Timber.w(iae);
}
}
} else {
// null list, or size doesn't match request - DB modified between count and load
invalidate();
}
}

@Override
public void loadRange(@NonNull LoadRangeParams params,
@NonNull LoadRangeCallback<T> callback) {
// Left empty
}

@Nullable
public List<T> loadRange(int startPosition, int loadCount) {
final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mLimitOffsetQuery,
mSourceQuery.getArgCount() + 2);
sqLiteQuery.copyArgumentsFrom(mSourceQuery);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount);
sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition);
Cursor cursor = mDb.query(sqLiteQuery);
//noinspection TryFinallyCanBeTryWithResources
try {
return convertRows(cursor);
} finally {
cursor.close();
sqLiteQuery.release();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import android.annotation.SuppressLint;
import android.database.Cursor;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
Expand All @@ -11,6 +10,7 @@
import androidx.room.RoomDatabase;
import androidx.room.RoomSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteQuery;
import timber.log.Timber;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -91,7 +91,12 @@ public void loadInitial(@NonNull LoadInitialParams params,

List<T> list = loadRange(firstLoadPosition, firstLoadSize);
if (list != null && list.size() == firstLoadSize) {
callback.onResult(list, firstLoadPosition, totalCount);
try {
callback.onResult(list, firstLoadPosition, totalCount);
} catch (IllegalArgumentException e) {
// workaround with paging incorrect tiling
Timber.w(e);
}
} else {
// null list, or size doesn't match request - DB modified between count and load
invalidate();
Expand Down

0 comments on commit c972184

Please sign in to comment.