Wenn die softwareseitige Tastatur öffnet, wird nur noch zu Position 0 gescrollt, wenn man da drauf oder drunter ist mit der unteren Kante des Textfeldes für die Nachricht.

This commit is contained in:
Lyndon
2018-07-20 20:07:57 +02:00
parent 8bef7cf51a
commit 7afa569e87
24 changed files with 332 additions and 187 deletions

View File

@@ -33,8 +33,8 @@ android {
applicationId "de.beyondsoft.ownchat"
minSdkVersion 19
targetSdkVersion 27
versionCode 16
versionName "1.16"
versionCode 18
versionName "1.18"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}

View File

@@ -84,6 +84,7 @@ public class MessageRealmRepository implements Repository<Message> {
public Completable update(Iterable<Message> items, Specification specification) {
final RealmWrapper realmWrapper = new RealmWrapper();
MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
return Observable.from(items)
.doOnSubscribe(() -> {
realmWrapper.realm = mRealmProvider.provide();
@@ -114,6 +115,7 @@ public class MessageRealmRepository implements Repository<Message> {
public Observable<List<Message>> updateMessages(Iterable<Message> items, Specification specification) {
final RealmWrapper realmWrapper = new RealmWrapper();
MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
return Observable.from(items)
.flatMapIterable((Func1<Message, Iterable<Message>>) message -> items)
.map(mToRealmMapper::from)
@@ -147,6 +149,7 @@ public class MessageRealmRepository implements Repository<Message> {
public Completable remove(Specification specification) {
RealmWrapper realmWrapper = new RealmWrapper();
MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
.doOnSubscribe(() -> {
realmWrapper.realm = mRealmProvider.provide();
@@ -167,10 +170,8 @@ public class MessageRealmRepository implements Repository<Message> {
@Override
public Completable removeAll() {
RealmWrapper realmWrapper = new RealmWrapper();
return Observable.fromCallable((Callable<Void>) () -> {
realmWrapper.realm.delete(MessageRealm.class);
return null;
})
return Observable.fromCallable((Callable<Void>) () -> { realmWrapper.realm.delete(MessageRealm.class); return null; })
.doOnSubscribe(() -> {
realmWrapper.realm = mRealmProvider.provide();
realmWrapper.realm.beginTransaction();
@@ -191,6 +192,7 @@ public class MessageRealmRepository implements Repository<Message> {
RealmWrapper realmWrapper = new RealmWrapper();
MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
((MessageByIdSpecification) specification).setMessageId(id);
return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
.map(mToMessageMapper::from)
.doOnSubscribe(() -> {
@@ -211,6 +213,7 @@ public class MessageRealmRepository implements Repository<Message> {
public Observable<List<Message>> queryForAll(Specification specification) {
RealmWrapper realmWrapper = new RealmWrapper();
RealmSpecification<MessageRealm> realmSpecification = (RealmSpecification) specification;
return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm)
.findAll()
.sort(MessageRealm.CREATED_AT, Sort.DESCENDING))
@@ -230,5 +233,4 @@ public class MessageRealmRepository implements Repository<Message> {
realmWrapper.realm.close();
});
}
}

View File

@@ -15,6 +15,7 @@ public class MessageRealm extends RealmObject {
public static final String CREATED_AT = "createdAt";
public static final String USER_ID = "userId";
public static final String MESSAGE = "message";
public static final String FILENAME = "filename";
@PrimaryKey
private String uniqueId;

View File

@@ -23,8 +23,12 @@ public class MessagesByGroupAndSearchTextSpecification implements RealmSpecifica
@Override
public RealmQuery<MessageRealm> toRealmQuery(Realm realm) {
return realm.where(MessageRealm.class)
.contains(MessageRealm.MESSAGE, mSearchText, Case.INSENSITIVE)
.equalTo(MessageRealm.GROUP_ID, mGroupId)
.equalTo(MessageRealm.USER_ID, mUserId);
.equalTo(MessageRealm.USER_ID, mUserId)
.beginGroup()
.contains(MessageRealm.MESSAGE, mSearchText, Case.INSENSITIVE)
.or()
.contains(MessageRealm.FILENAME, mSearchText, Case.INSENSITIVE)
.endGroup();
}
}

View File

@@ -23,7 +23,7 @@ import de.beyondsoft.ownchat.filepicker.models.Document;
import de.beyondsoft.ownchat.filepicker.models.FileType;
import de.beyondsoft.ownchat.filepicker.utils.MediaStoreHelper;
import de.beyondsoft.ownchat.filepicker.utils.TabLayoutHelper;
import de.beyondsoft.ownchat.filepicker.utils.Utils;
import de.beyondsoft.ownchat.filepicker.utils.FilePickerUtils;
/**
* Created by Lyndon on 26-Oct-2017.
@@ -146,6 +146,6 @@ public class DocPickerFragment extends BaseFragment {
private ArrayList<Document> filterDocuments(final String[] type, List<Document> documents) {
final Predicate<Document> docType = document -> document.isThisType(type);
return new ArrayList<>(Utils.filter(new HashSet<>(documents),docType));
return new ArrayList<>(FilePickerUtils.filter(new HashSet<>(documents),docType));
}
}

View File

@@ -3,7 +3,7 @@ package de.beyondsoft.ownchat.filepicker.models;
import android.os.Parcel;
import android.os.Parcelable;
import de.beyondsoft.ownchat.filepicker.utils.Utils;
import de.beyondsoft.ownchat.filepicker.utils.FilePickerUtils;
/**
* Created by Lyndon on 26-Oct-2017.
@@ -42,7 +42,7 @@ public class BaseFile implements Parcelable {
public boolean isImage() {
String[] types = {"jpg","jpeg","png","gif"};
return Utils.contains(types, this.path);
return FilePickerUtils.contains(types, this.path);
}
@Override

View File

@@ -2,7 +2,7 @@ package de.beyondsoft.ownchat.filepicker.models;
import java.io.File;
import de.beyondsoft.ownchat.filepicker.utils.Utils;
import de.beyondsoft.ownchat.filepicker.utils.FilePickerUtils;
/**
* Created by Lyndon on 26-Oct-2017.
@@ -78,7 +78,7 @@ public class Document extends BaseFile {
public boolean isThisType(String[] types)
{
return Utils.contains(types, this.path);
return FilePickerUtils.contains(types, this.path);
}
public FileType getFileType()

View File

@@ -12,18 +12,16 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
/**
* Created by Lyndon on 26-Oct-2017.
*/
public class Utils {
public class FilePickerUtils {
public static <T> Collection<T> filter(Collection<T> target, Predicate<T> predicate) {
Collection<T> result = new ArrayList<T>();
for (T element: target) {
if (predicate.apply(element)) {
Collection<T> result = new ArrayList<>();
for(T element : target) {
if(predicate.apply(element)) {
result.add(element);
}
}
return result;
}
@@ -31,15 +29,16 @@ public class Utils {
String name = file.getName();
try {
return name.substring(name.lastIndexOf(".") + 1);
} catch (Exception e) {
} catch(Exception e) {
return "";
}
}
public static boolean contains(String[] types, String path) {
for (String string : types) {
if (path.toLowerCase().endsWith(string)) return true;
for(String string : types) {
if(path.toLowerCase().endsWith(string)) return true;
}
return false;
}
@@ -59,15 +58,14 @@ public class Utils {
widthHeight[WIDTH_INDEX] = size.x;
widthHeight[HEIGHT_INDEX] = size.y;
if (!isScreenSizeRetrieved(widthHeight)) {
if(!isScreenSizeRetrieved(widthHeight)) {
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
widthHeight[0] = metrics.widthPixels;
widthHeight[1] = metrics.heightPixels;
}
// Last defense. Use deprecated API that was introduced in lower than API 13
if (!isScreenSizeRetrieved(widthHeight)) {
if(!isScreenSizeRetrieved(widthHeight)) {
widthHeight[0] = display.getWidth(); // deprecated
widthHeight[1] = display.getHeight(); // deprecated
}

View File

@@ -1,72 +0,0 @@
package de.beyondsoft.ownchat.filepicker.utils;
import android.text.TextUtils;
import java.io.File;
import de.beyondsoft.ownchat.R;
import de.beyondsoft.ownchat.filepicker.FilePickerConst;
/**
* Created by Lyndon on 26-Oct-2017.
*/
public class FileUtils {
public static int getTypeDrawable(String path) {
return R.drawable.document;
}
public static FilePickerConst.FILE_TYPE getFileType(String path) {
String fileExtension = Utils.getFileExtension(new File(path));
if(TextUtils.isEmpty(fileExtension)) {
return FilePickerConst.FILE_TYPE.UNKNOWN;
}
if(isExcelFile(path)) {
return FilePickerConst.FILE_TYPE.EXCEL;
}
if(isDocFile(path)) {
return FilePickerConst.FILE_TYPE.WORD;
}
if(isPPTFile(path)) {
return FilePickerConst.FILE_TYPE.PPT;
}
if(isPDFFile(path)) {
return FilePickerConst.FILE_TYPE.PDF;
}
if(isTxtFile(path)) {
return FilePickerConst.FILE_TYPE.TXT;
} else {
return FilePickerConst.FILE_TYPE.UNKNOWN;
}
}
public static boolean isExcelFile(String path) {
String[] types = {"xls","xlsx"};
return Utils.contains(types, path);
}
public static boolean isDocFile(String path) {
String[] types = {"doc","docx", "dot","dotx"};
return Utils.contains(types, path);
}
public static boolean isPPTFile(String path) {
String[] types = {"ppt","pptx"};
return Utils.contains(types, path);
}
public static boolean isPDFFile(String path) {
String[] types = {"pdf"};
return Utils.contains(types, path);
}
public static boolean isTxtFile(String path) {
String[] types = {"txt"};
return Utils.contains(types, path);
}
}

View File

@@ -64,6 +64,8 @@ import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.TextView;
@@ -71,6 +73,7 @@ import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -174,6 +177,7 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
private Rect mDialogContainer;
private int mInitialLeft = 0;
private int mInitialMarginLeft = 0;
private int mCurrentVisibleItemPosition = -1;
@Inject
@Named(AppPrefsConstants.CHAT_OPENED)
@@ -193,12 +197,20 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mSendMessageImageView.setEnabled(false);
InjectionHelper.getChatComponent(this).inject(this);
mBaseChatAdapter = getAdapter();
mEmptyLayout.setOnTryAgainClickListener(v -> mCallback.fetchMoreMessagesByGroupId());
mEmptyLayout.setOnTryAgainClickListener(v -> {
mCallback.fetchMoreMessagesByGroupId();
});
setupRecyclerView();
EmojIconActions emojiIcon = new EmojIconActions(this, mCoordinatorLayout, mChatMessageEmojiEditText, mEmojiButton);
emojiIcon.ShowEmojIcon();
mChatMessageEmojiEditText.setOnClickListener(view -> {
mCurrentVisibleItemPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
});
showLoading();
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener);
@@ -218,10 +230,14 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mEndlessScrollListener = new EndlessScrollListener(VISIBLE_THRESHOLD, linearLayoutManager) {
@Override
public boolean onLoad() {
if(mCallback == null || mCallback.isLoading()) {
if(mCallback == null || mCallback.isLoading() || mIsSearching || mIsJumpingToMessage) {
return false;
}
mIsJumpingToMessage = false;
Log.e("SCROLLING_LOG", "Fetching messages in onLoad");
mCallback.fetchMoreMessagesByGroupId();
return true;
}
@@ -253,15 +269,26 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
@Override
public void messagesLoaded(ArrayList<Message> messages, boolean isFirstPage) {
if(isFirstPage) {
Log.d("SCROLLING_LOG", "messagesLoaded with first page");
mEndlessScrollListener.reset();
mBaseChatAdapter.setMessages(messages);
mRecyclerView.scrollToPosition(0);
return;
}
Log.d("SCROLLING_LOG", "messagesLoaded");
mBaseChatAdapter.addMessages(messages);
}
@Override
public void messagesLoaded(ArrayList<Message> messages) {
mEndlessScrollListener.reset();
mBaseChatAdapter.setMessages(messages);
}
private void shareFile(boolean isFile) {
mIsComingFromSharingActivity.set(false);
@@ -336,7 +363,7 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
@Override
public void hideMessagesLoading() {
isSearching = false;
mIsSearching = false;
mBaseChatAdapter.setLoading(false);
@@ -695,6 +722,35 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mCallback.resendMessage(message, !TextUtils.isEmpty(message.filePath) ? getContentResolver().getType(Uri.parse(message.filePath)) : "");
}
private boolean mIsJumpingToMessage;
@Override
public void jumpToMessage(Message message) {
if(mChatSearchView.isIconified()) {
return;
}
if(!mChatPresenter.isAttached()) {
mChatPresenter.attachView(this);
}
Log.i("SCROLLING_LOG", "Jumping to message");
mCallback.loadMessagesInContext(mGroup.id, message.createdAt, message);
}
@Override
public void scrollToMessage(Message message) {
try {
final int position = mBaseChatAdapter.getPosition(message);
mIsJumpingToMessage = true;
Log.i("SCROLLING_LOG", "About to scroll to position " + position);
mRecyclerView.postDelayed(() -> mRecyclerView.smoothScrollToPosition(position), 350);
} catch(Exception e) {
Log.e("SCROLL_TO_MESSAGE", e.getMessage());
}
}
@Override
public MediaType getMediaType(Uri fileUri) {
return MediaType.parse(getContentResolver().getType(fileUri));
@@ -777,6 +833,8 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
@Override
public void addOneMessage(Message message) {
Log.i("SCROLLING_LOG", "Adding message '" + message.message + "'");
mBaseChatAdapter.addMessage(message);
if(mEndlessScrollListener.getVisibleItem() == 0) {
@@ -812,32 +870,39 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
}
}
static int mAppHeight;
private ViewTreeObserver.OnGlobalLayoutListener mKeyboardListener = new ViewTreeObserver.OnGlobalLayoutListener() {
private boolean mKeyboardShown = false;
private int mPreviousHeight;
@Override
public void onGlobalLayout() {
Rect r = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
int screenHeight = getWindow().getDecorView().getHeight();
int newHeight = mRecyclerView.getHeight();
// r.bottom is the position above soft keypad or device button.
// if keypad is shown, the r.bottom is smaller than that before.
int keypadHeight = screenHeight - r.bottom;
if(mPreviousHeight == newHeight) {
return;
}
if(keypadHeight > screenHeight * 0.15 && !mKeyboardShown) { // 0.15 ratio is perhaps enough to determine keypad height.
// keyboard is opened
mRecyclerView.scrollToPosition(0);
mKeyboardShown = true;
} else {
mKeyboardShown = false;
mPreviousHeight = newHeight;
if(newHeight >= mAppHeight) {
mAppHeight = newHeight;
}
if(newHeight != 0) {
if(mAppHeight > newHeight && mCurrentVisibleItemPosition <= 0) {
mRecyclerView.scrollToPosition(0);
}
}
}
};
@Override
public void onBackPressed() {
if(!mChatSearchView.isIconified()) {
closeSearchView();
return;
}
mIsComingFromSharingActivity.set(false);
mSelectedFilePath.delete();
@@ -866,20 +931,7 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
return false;
}
public boolean isSearching = false;
private String mSearchText;
@Override
public String getSearchText() {
return mSearchText;
}
@Override
public void resetSearchText() {
mSearchText = null;
isSearching = false;
}
public boolean mIsSearching = false;
@Override
public void showEmptySearchResultMessage(String text) {
@@ -888,12 +940,12 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mEmptyLayout.setEmptyText(emptyText);
mEmptyLayout.showEmpty();
isSearching = false;
mIsSearching = false;
}
@Override
public boolean onQueryTextChange(String newText) {
if(isSearching && !TextUtils.isEmpty(newText)) {
if(mIsSearching && !TextUtils.isEmpty(newText)) {
return false;
}
@@ -906,14 +958,13 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mCallback.resetMessages();
}
isSearching = true;
mIsSearching = true;
if(TextUtils.isEmpty(newText)) {
loadMessages();
} else {
mSearchText = newText;
mBaseChatAdapter.setMessages(new ArrayList<>());
messagesLoaded(new ArrayList<>(), true);
loadSearchResult(newText);
}
}, 300);
@@ -927,4 +978,6 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mChatSearchView.setIconified(false);
}
}
public abstract void closeSearchView();
}

View File

@@ -9,6 +9,7 @@ import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
@@ -41,10 +42,11 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
private long mUserId;
final ArrayList<Message> mMessages = new ArrayList<>();
final ArrayList<Message> mMessages = new ArrayList<>();
private final List<Integer> mDatePositions = new ArrayList<>();
final List<Date> mDateList = new ArrayList<>();
final List<Integer> mPositionOffset = new ArrayList<>();
final List<Date> mDateList = new ArrayList<>();
final List<Integer> mPositionOffset = new ArrayList<>();
private boolean mIsLoading;
private int mLastOffset = 0;
private Calendar mCurrentDay;
@@ -64,6 +66,7 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
mDatePositions.clear();
mDateList.clear();
mPositionOffset.clear();
mIsLoading = false;
mLastOffset = 0;
mCurrentDay = Calendar.getInstance();
@@ -80,6 +83,7 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
for(int i = 1; i < messageSize; i++) {
mCurrentDay.setTimeInMillis(messages.get(i).createdAt);
mLastDay.setTimeInMillis(messages.get(i - 1).createdAt);
if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) {
mDatePositions.add(i + mLastOffset);
mPositionOffset.add(0);
@@ -92,6 +96,12 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
mMessages.addAll(messages);
String[] messageTexts = new String[mMessages.size()];
for(int i = 0; i < messageTexts.length; i++) {
messageTexts[i] = mMessages.get(i).message;
}
notifyDataSetChanged();
}
@@ -103,6 +113,7 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
int currentMessagesCount = mMessages.size();
int lastOffsetBeforeParsing = mLastOffset;
int datesInserted = 0;
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(messages.get(0).createdAt);
@@ -219,6 +230,25 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
}
public int getPosition(Message message) {
Log.d("MESSAGE_SCROLL", "message id: " + message.id);
int position = 0;
for(int i = 0; i < mMessages.size(); i++) {
if(message.equals(mMessages.get(i))) {
position = i;
break;
}
}
Log.d("MESSAGE_SCROLL", "position: " + position + "; offset: " + mPositionOffset.get(position));
position += mPositionOffset.get(position);
return position;
}
@Override
public int getItemCount() {
return mMessages.size() + mLastOffset + (mIsLoading ? 1 : 0);
@@ -252,8 +282,7 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
return LOADING_TYPE;
}
return mMessages.get(position - mPositionOffset.get(position)).senderId == mUserId ?
RIGHT_CHAT_TYPE : LEFT_CHAT_TYPE;
return mMessages.get(position - mPositionOffset.get(position)).senderId == mUserId ? RIGHT_CHAT_TYPE : LEFT_CHAT_TYPE;
}
void setMessageClickListener(MessageClickListener messageClickListener) {
@@ -405,11 +434,29 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
@BindView(R.id.chat_item_file_name)
TextView filenameTextView;
@BindView(R.id.chat_item_container)
RelativeLayout chatItemContainer;
MessageViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Optional
@OnClick(R.id.chat_item_container)
void jumpToMessage() {
int currentPosition = getAdapterPosition();
int position = currentPosition - mPositionOffset.get(currentPosition);
if(position >= mMessages.size()) {
position = 0;
}
Message messageToJumpTo = mMessages.get(position);
mMessageClickListener.jumpToMessage(messageToJumpTo);
}
@Optional
@OnClick(R.id.chat_item_sending_failed)
void retrySendingClicked() {
@@ -471,5 +518,7 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
void localFileClicked(Uri uri);
void resendMessage(Message message);
void jumpToMessage(Message message);
}
}

View File

@@ -8,9 +8,6 @@ import dagger.Module;
import dagger.Provides;
import retrofit2.Retrofit;
/**
* Created by imarneanu on 3/1/17.
*/
@Module
public class ChatModule {

View File

@@ -303,17 +303,21 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
private void loadMessagesFromDBByGroupId() {
MessageByGroupIdSpecification messageByGroupIdSpecification = new MessageByGroupIdSpecification(mUserIdPreference.get());
messageByGroupIdSpecification.setGroupId(mGroupId);
mMessageRealmRepository.queryForAll(messageByGroupIdSpecification)
.map(ArrayList::new)
.compose(RxUtils.provideDefaultTransformer())
.subscribe(this::loadMessagesFromDBSuccessful, this::loadMessagesError);
}
private String mText;
public void searchMessagesFromDBByGroupIdAndText(long groupId, String text) {
MessagesByGroupAndSearchTextSpecification messagesByGroupAndSearchTextSpecification = new MessagesByGroupAndSearchTextSpecification(mUserIdPreference.get());
messagesByGroupAndSearchTextSpecification.setGroupId(groupId);
messagesByGroupAndSearchTextSpecification.setSearchText(text);
mText = text;
mMessageRealmRepository.queryForAll(messagesByGroupAndSearchTextSpecification)
.map(ArrayList::new)
.compose(RxUtils.provideDefaultTransformer())
@@ -332,14 +336,9 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
getView().hideLoading();
getView().hideMessagesLoading();
String searchText = getView().getSearchText();
getView().resetSearchText();
if(!mMessages.isEmpty()) {
int endOffset = mOffset + LIMIT > mMessages.size() ? mMessages.size() : mOffset + LIMIT;
if(mOffset > endOffset) {
Log.i("", "");
return;
}
@@ -373,7 +372,92 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
return;
}
getView().showEmptySearchResultMessage(searchText);
getView().showEmptySearchResultMessage(mText);
}
private Message mMessageToScrollTo;
public void loadMessagesInContext(long groupId, long createdAt, Message messageToScrollTo) {
MessageByGroupIdSpecification messageByGroupIdSpecification = new MessageByGroupIdSpecification(mUserIdPreference.get());
messageByGroupIdSpecification.setGroupId(groupId);
mMessageToScrollTo = messageToScrollTo;
mMessageRealmRepository.queryForAll(messageByGroupIdSpecification)
.map(ArrayList::new)
.compose(RxUtils.provideDefaultTransformer())
.subscribe(this::loadMessagesInContextSuccessful, this::loadMessagesError);
}
private void loadMessagesInContextSuccessful(ArrayList<Message> messages) {
if(!isAttached()) {
return;
}
Log.w("SCROLLING_LOG" ,"Count of messages loaded: " + messages.size());
mMessages.clear();
mMessages.addAll(messages);
mLoading = false;
getView().hideLoading();
getView().hideMessagesLoading();
if(!mMessages.isEmpty()) {
int tempLimit = (LIMIT - 1) / 2;
int position = 0;
mOffset = 0;
for(Message message : mMessages) {
if(message.equals(mMessageToScrollTo)) {
position = mMessages.indexOf(message);
}
}
mOffset = position < tempLimit ? 0 : position - tempLimit;
int endOffset = mOffset + LIMIT > mMessages.size() ? mMessages.size() : mOffset + LIMIT;
if(mOffset > endOffset) {
return;
}
double pos = (double) position;
double lim = (double) LIMIT;
mPage = (int) Math.ceil(pos / lim);
boolean isFirstPage = mPage == FIRST_PAGE;
getView().messagesLoaded(new ArrayList<>(mMessages.subList(mOffset, endOffset)));
if(isFirstPage && mSystemUtils.isNetworkUnavailable()) {
Message firstMessage = mMessages.get(0);
Message message = new Message(firstMessage.id,
firstMessage.groupId,
firstMessage.createdAt / 1000,
firstMessage.message,
firstMessage.filePath,
firstMessage.smallerImage,
firstMessage.originalFilename,
firstMessage.uri,
firstMessage.senderId,
firstMessage.senderName,
firstMessage.senderAvatar,
firstMessage.status,
true,
firstMessage.userId,
firstMessage.fileDownloaded);
storeGroupLocally(mGroupId, message);
}
//mPage++;
mOffset += LIMIT;
getView().scrollToMessage(mMessageToScrollTo);
return;
}
getView().showEmptyMessage();
}
/**
@@ -393,6 +477,9 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
if(!mMessages.isEmpty()) {
int endOffset = mOffset + LIMIT > mMessages.size() ? mMessages.size() : mOffset + LIMIT;
LogFetchingInformation(mOffset, endOffset, LIMIT, mMessages.size());
if(mOffset > endOffset) {
return;
}
@@ -447,6 +534,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
public void fetchMoreMessagesByGroupId() {
if(mMessages.size() < 25) {
getView().hideMessagesLoading();
return;
}
@@ -461,6 +549,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
if(mSystemUtils.isNetworkUnavailable()) {
loadMessagesFromDBByGroupId();
getView().showError(R.string.error_no_internet_connection);
return;
}
@@ -1160,4 +1249,20 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
private static String generateRandomId() {
return "_" + new Random().nextInt();
}
private static void LogFetchingInformation(int offset, int endOffset, int limit, int messageCollectionSize) {
Log.i("FETCHING_INFORMATION", "\n-" +
"\nOffset: " + offset +
"\nEnd offset: " + endOffset +
"\nNumber of messages: " + messageCollectionSize +
"\nLimit: " + limit +
"\noffset > end offset: " + (offset > endOffset)
);
}
private static void LogMessageCollection(List<Message> messages) {
for(int i = 0; i < messages.size(); i++) {
Log.d("MESSAGES_LOG", String.format("%03d", i) + " " + messages.get(i).id + ": " + (messages.get(i).message == null ? messages.get(i).originalFilename : messages.get(i).message));
}
}
}

View File

@@ -12,15 +12,14 @@ import java.util.ArrayList;
import okhttp3.MediaType;
/**
* Created by Rares Teodorescu on 21/02/2017.
*/
interface ChatView extends MVPView {
void setCallback(Callback callback);
void messagesLoaded(ArrayList<Message> messages, boolean isFirstPage);
void messagesLoaded(ArrayList<Message> messages);
void showLoading();
void hideLoading();
@@ -57,11 +56,10 @@ interface ChatView extends MVPView {
void showEmptySearchResultMessage(String searchText);
String getSearchText();
void resetSearchText();
void scrollToMessage(Message message);
interface Callback {
void loadMessagesInContext(long groupId, long createdAt, Message messageToExclude);
void loadMessagesFromServerByGroupId(long groupId);

View File

@@ -195,4 +195,11 @@ public class GroupChatActivity extends BaseChatActivity {
cancelNotification();
}
public void closeSearchView() {
mGroupMembersTextView.setVisibility(View.VISIBLE);
mGroupTitleTextView.setVisibility(View.VISIBLE);
mChatImageView.setVisibility(View.VISIBLE);
mChatSearchView.setIconified(true);
}
}

View File

@@ -16,10 +16,8 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
@@ -201,4 +199,10 @@ public class SingleChatActivity extends BaseChatActivity {
}
}
};
public void closeSearchView() {
mSingleChatTitleTextView.setVisibility(View.VISIBLE);
mChatImageView.setVisibility(View.VISIBLE);
mChatSearchView.setIconified(true);
}
}

View File

@@ -5,6 +5,7 @@ import de.beyondsoft.ownchat.model.Message;
import de.beyondsoft.ownchat.utils.DateUtils;
import de.beyondsoft.ownchat.utils.ui.ViewUtils;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -19,8 +20,9 @@ class SingleChatAdapter extends BaseChatAdapter {
super(userId);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if(viewType == LEFT_CHAT_TYPE) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_single_chat_left, parent, false);
@@ -49,7 +51,7 @@ class SingleChatAdapter extends BaseChatAdapter {
@SuppressWarnings("ConstantConditions")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
int viewType = getItemViewType(position);
switch(viewType) {
case LOADING_TYPE:
@@ -91,5 +93,4 @@ class SingleChatAdapter extends BaseChatAdapter {
break;
}
}
}

View File

@@ -2,11 +2,12 @@ package de.beyondsoft.ownchat.utils;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
public abstract class EndlessScrollListener extends RecyclerView.OnScrollListener {
private int mPreviousTotal = 0;
private int mVisibleThreshold = 5;
private int mVisibleThreshold;
private boolean mLoading = false;
private final LinearLayoutManager mLayoutManager;
private int mVisibleItem;
@@ -37,6 +38,8 @@ public abstract class EndlessScrollListener extends RecyclerView.OnScrollListene
return;
}
Log.e("SCROLLING_LOG", "Scrolling " + (dy < 0 ? "up" : "down"));
recyclerView.post(() -> {
if(!mLoading && (totalItemCount - visibleItemCount) <= (mVisibleItem + mVisibleThreshold)) {
mLoading = onLoad();

View File

@@ -1,10 +1,7 @@
package de.beyondsoft.ownchat.utils.ui;
import de.beyondsoft.ownchat.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.os.Build;
@@ -13,12 +10,8 @@ import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import de.beyondsoft.ownchat.R;
/**
* Helper methods for view related classes
* <p>
* Created by BoldijarPaul on 03/08/16.
*/
public abstract class ViewUtils {
private ViewUtils() {

View File

@@ -21,13 +21,13 @@
tools:src="@drawable/avatar" />
<RelativeLayout
android:id="@+id/chat_item_container"
style="@style/LeftChatItemRelativeLayout"
android:padding="7dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_toEndOf="@id/chat_item_contact_image"
>
android:padding="7dp">
<TextView
android:id="@+id/chat_item_time"
@@ -38,7 +38,7 @@
android:textSize="10sp"
tools:ignore="SmallSp"
tools:text="Sasha Grey | 15:23"
tools:visibility="visible"/>
tools:visibility="visible" />
<ImageView
android:id="@+id/chat_item_file"
@@ -70,14 +70,13 @@
android:id="@+id/chat_item_progress"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_alignParentStart="true"
android:layout_below="@id/chat_item_time"
android:layout_marginTop="4dp"
android:layout_alignParentStart="true"
android:visibility="gone"
custom:donut_finished_color="@color/colorDefault"
custom:donut_text_color="@color/colorBlack"
tools:visibility="visible"
/>
tools:visibility="visible" />
<hani.momanii.supernova_emoji_library.Helper.EmojiconTextView
android:id="@+id/chat_item_message"

View File

@@ -22,12 +22,13 @@
tools:src="@drawable/avatar" />
<RelativeLayout
android:id="@+id/chat_item_container"
style="@style/RightChatItemRelativeLayout"
android:padding="7dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_toEndOf="@id/chat_item_contact_image">
android:layout_toEndOf="@id/chat_item_contact_image"
android:padding="7dp">
<TextView
android:id="@+id/chat_item_sending_failed"
@@ -53,7 +54,7 @@
android:layout_marginStart="8dp"
android:layout_toEndOf="@+id/chat_item_time"
android:visibility="gone"
tools:visibility="visible"/>
tools:visibility="visible" />
<ImageView
android:id="@+id/chat_item_message_status"

View File

@@ -12,10 +12,11 @@
>
<RelativeLayout
android:id="@+id/chat_item_container"
style="@style/LeftChatItemRelativeLayout"
android:padding="7dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:padding="7dp">
<TextView
android:id="@+id/chat_item_time"
@@ -59,7 +60,7 @@
android:visibility="gone"
custom:donut_finished_color="@color/colorDefault"
custom:donut_text_color="@color/colorBlack"
tools:visibility="visible"/>
tools:visibility="visible" />
<hani.momanii.supernova_emoji_library.Helper.EmojiconTextView
android:id="@+id/chat_item_message"

View File

@@ -13,6 +13,7 @@
android:paddingStart="0dp">
<RelativeLayout
android:id="@+id/chat_item_container"
style="@style/RightChatItemRelativeLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -46,7 +47,6 @@
<ImageView
android:id="@+id/chat_item_message_status"
tools:src="@drawable/delivered"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/chat_item_time"
@@ -55,8 +55,8 @@
android:layout_toEndOf="@+id/chat_item_time"
android:contentDescription="@null"
android:visibility="gone"
tools:visibility="visible"
/>
tools:src="@drawable/delivered"
tools:visibility="visible" />
<TextView
android:id="@+id/chat_item_time"
@@ -93,7 +93,7 @@
android:visibility="gone"
custom:donut_finished_color="@color/colorWhite"
custom:donut_text_color="@color/colorWhite"
tools:visibility="visible"/>
tools:visibility="visible" />
<TextView
android:id="@+id/chat_item_file_name"

View File

@@ -127,6 +127,7 @@
<item name="android:textColorPrimary">@color/colorBlack</item>
<!--This line changes the color of icons in toolbar (back, overflow menu icons)-->
<item name="android:textColorSecondary">@color/colorBlack</item>
<item name="colorAccent">@color/colorBlack</item>
</style>
</resources>