Suche mit der Fähigkeit herunterzuscrollen. Läuft allerdings noch nicht optimal.

This commit is contained in:
Lyndon
2018-09-05 16:56:30 +02:00
parent 7afa569e87
commit 9855b6e91a
17 changed files with 398 additions and 149 deletions

View File

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

View File

@@ -17,6 +17,7 @@ import de.beyondsoft.ownchat.di.scopes.ApplicationScope;
import de.beyondsoft.ownchat.utils.Constants;
import java.io.File;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
@@ -113,28 +114,33 @@ public class ApiModule {
boolean isVerified = false;
if(url.toString().contains(".ownchat.de")) {
for(Certificate cert : certificates) {
X509Certificate x509 = (X509Certificate) cert;
if(certificates.size() < 2) {
throw new SSLHandshakeException("Insufficient number of certificates");
}
String name = x509.getIssuerX500Principal().getName();
String serialNumber = x509.getSerialNumber().toString();
X509Certificate cert1 = (X509Certificate) certificates.get(0);
X509Certificate cert2 = (X509Certificate) certificates.get(1);
StringWriter writer = new StringWriter();
String cert1_subject_name = cert1.getSubjectX500Principal().getName();
String cert1_issuer_name = cert1.getIssuerX500Principal().getName();
PEMWriter pemWriter = new PEMWriter(writer);
pemWriter.writeObject(x509.getPublicKey());
pemWriter.flush();
pemWriter.close();
String cert2_subject_name = cert2.getSubjectX500Principal().getName();
String writerAsString = writer.toString();
String base64EncodedKey = writerAsString.split("-----BEGIN PUBLIC KEY-----")[1].split("-----END PUBLIC KEY-----")[0].replaceAll(System.getProperty("line.separator"), "");
StringWriter cert2_writer = new StringWriter();
PEMWriter cert2_pemWriter = new PEMWriter(cert2_writer);
if(name.contains("CN=Let's Encrypt Authority X3") && name.contains("O=Let's Encrypt") && (base64EncodedKey.equals(Constants.PUBLIC_KEY) || base64EncodedKey.equals(Constants.DICTIONARY_PUBLIC_KEY))) {
isVerified = true;
break;
} else {
Log.e(LOG_TAG_NETWORK_INTERCEPTOR, "\nSSL certificates are invalid:\nName: " + name + "\\nUrl: " + url + "\n Expected public key: " + Constants.PUBLIC_KEY + "\nActual public key: " + base64EncodedKey + "\n" + writerAsString);
}
cert2_pemWriter.writeObject(cert2.getPublicKey());
cert2_pemWriter.flush();
cert2_pemWriter.close();
if(cert1_subject_name.contains(".ownchat.de") && cert1_issuer_name.contains("CN=Let's Encrypt Authority X3") && cert2_subject_name.contains("CN=Let's Encrypt Authority X3")) {
String cert2_writerAsString = cert2_writer.toString();
String cert2_base64EncodedKey = cert2_writerAsString.split("-----BEGIN PUBLIC KEY-----")[1].split("-----END PUBLIC KEY-----")[0].replaceAll(System.getProperty("line.separator"), "");
isVerified = cert2_base64EncodedKey.equals(Constants.PUBLIC_KEY);
} else {
Log.e(LOG_TAG_NETWORK_INTERCEPTOR, "SSL certificates are invalid!");
throw new SSLHandshakeException("Invalid SSL certificates");
}
}

View File

@@ -77,6 +77,8 @@ public class HandleMessagesService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Log.i("FIREBASE_RECEIVER", "onMessageReceived called");
InjectionHelper.getMessagingComponent(this).inject(HandleMessagesService.this);
customizeNotification(remoteMessage.getData());

View File

@@ -12,6 +12,7 @@ import de.beyondsoft.ownchat.di.InjectionHelper;
import de.beyondsoft.ownchat.filepicker.FilePickerBuilder;
import de.beyondsoft.ownchat.filepicker.FilePickerConst;
import de.beyondsoft.ownchat.filepicker.utils.Orientation;
import de.beyondsoft.ownchat.utils.BeyondSoftLog;
import de.beyondsoft.ownchat.utils.ui.TouchableImageView;
import de.beyondsoft.ownchat.model.Group;
import de.beyondsoft.ownchat.model.Message;
@@ -64,8 +65,6 @@ 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;
@@ -73,8 +72,9 @@ import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -236,14 +236,44 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mIsJumpingToMessage = false;
Log.e("SCROLLING_LOG", "Fetching messages in onLoad");
mCallback.fetchMoreMessagesByGroupId();
return true;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
BeyondSoftLog.logScrollingEvent_I("onScrolled");
if(mCallback != null) {
mCallback.setIsScrollingDown(dy > 0);
if(mGroup != null && mBaseChatAdapter != null && mBaseChatAdapter.mMessages.size() > 0) {
mHasNewerMessages = mCallback.hasNewerMessages(mGroup.id, mBaseChatAdapter.mMessages.get(0).id);
}
}
super.onScrolled(recyclerView, dx, dy);
}
};
mRecyclerView.addOnScrollListener(mEndlessScrollListener);
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mIsJumpingToMessage = false;
return false;
}
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
});
}
@OnClick(R.id.chat_toolbar_extra_image)
@@ -269,8 +299,6 @@ 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);
@@ -278,9 +306,11 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
return;
}
Log.d("SCROLLING_LOG", "messagesLoaded");
mBaseChatAdapter.addMessages(messages);
if(!mCallback.getIsScrollingDown()) {
mBaseChatAdapter.addMessages(messages, mCallback.getIsScrollingDown());
} else {
mBaseChatAdapter.addMessagesWhenScrollingDown(messages);
}
}
@Override
@@ -722,6 +752,8 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mCallback.resendMessage(message, !TextUtils.isEmpty(message.filePath) ? getContentResolver().getType(Uri.parse(message.filePath)) : "");
}
private Message focusedMessage;
private boolean mIsJumpingToMessage;
@Override
public void jumpToMessage(Message message) {
@@ -733,7 +765,8 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
mChatPresenter.attachView(this);
}
Log.i("SCROLLING_LOG", "Jumping to message");
focusedMessage = message;
mCallback.loadMessagesInContext(mGroup.id, message.createdAt, message);
}
@@ -744,10 +777,9 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
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());
Log.e("SCROLL_TO_MESSAGE", e.getMessage(), e);
}
}
@@ -833,8 +865,6 @@ 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) {
@@ -925,7 +955,6 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
super.onNewIntent(intent);
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
@@ -980,4 +1009,11 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
}
public abstract void closeSearchView();
@Override
public void scrollToPositionZero() {
if(mRecyclerView != null) {
mRecyclerView.scrollToPosition(0);
}
}
}

View File

@@ -15,8 +15,10 @@ import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.github.lzyzsd.circleprogress.DonutProgress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -28,24 +30,25 @@ import butterknife.OnClick;
import butterknife.Optional;
import de.beyondsoft.ownchat.R;
import de.beyondsoft.ownchat.model.Message;
import de.beyondsoft.ownchat.utils.BeyondSoftLog;
import de.beyondsoft.ownchat.utils.DateUtils;
import de.beyondsoft.ownchat.utils.ui.GlideApp;
import de.beyondsoft.ownchat.utils.ui.ViewUtils;
import hani.momanii.supernova_emoji_library.Helper.EmojiconTextView;
abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
final static int DATE_TYPE = 0;
final static int LEFT_CHAT_TYPE = 1;
final static int DATE_TYPE = 0;
final static int LEFT_CHAT_TYPE = 1;
final static int RIGHT_CHAT_TYPE = 2;
final static int LOADING_TYPE = 3;
final static int LOADING_TYPE = 3;
private long mUserId;
final ArrayList<Message> mMessages = new ArrayList<>();
public 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<>();
public final List<Integer> mPositionOffset = new ArrayList<>();
private boolean mIsLoading;
private int mLastOffset = 0;
@@ -105,33 +108,94 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
notifyDataSetChanged();
}
void addMessages(ArrayList<Message> messages) {
// TODO: Clear the mDatePositions, mDateList, and mPositionOffset lists,
// TODO: but keep the mMessages list to populate it. Then recalculate the
// TODO: dates and the offsets.
// TODO:
void addMessagesWhenScrollingDown(ArrayList<Message> messages) {
mDatePositions.clear();
mDateList.clear();
mPositionOffset.clear();
mIsLoading = false;
mLastOffset = 0;
mCurrentDay = Calendar.getInstance();
mLastDay = Calendar.getInstance();
mIsRetrying = false;
if(!mMessages.isEmpty()) {
mPositionOffset.add(0);
mDateList.add(new Date(mMessages.get(0).createdAt));
}
for(Message message : messages) {
if(!mMessages.contains(message)) {
mMessages.add(message);
}
}
Collections.sort(mMessages, (m1, m2) -> Long.compare(m1.createdAt, m2.createdAt) * -1);
int messageSize = mMessages.size();
for(int i = 1; i < messageSize; i++) {
mCurrentDay.setTimeInMillis(mMessages.get(i).createdAt);
mLastDay.setTimeInMillis(mMessages.get(i - 1).createdAt);
if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) {
mDatePositions.add(i + mLastOffset);
mPositionOffset.add(0);
mDateList.add(mLastDay.getTime());
mLastOffset++;
}
mPositionOffset.add(mLastOffset);
mDateList.add(new Date(mMessages.get(i).createdAt));
}
String[] messageTexts = new String[mMessages.size()];
for(int i = 0; i < messageTexts.length; i++) {
messageTexts[i] = mMessages.get(i).message;
}
notifyDataSetChanged();
}
void addMessages(ArrayList<Message> messages, boolean isAddingNewerMessages) {
if(messages == null || messages.isEmpty() || mMessages.equals(messages)) {
return;
}
ArrayList<Message> filteredList = new ArrayList<>();
for(Message message : messages) {
if(!mMessages.contains(message)) {
filteredList.add(message);
}
}
int currentMessagesCount = mMessages.size();
int lastOffsetBeforeParsing = mLastOffset;
int datesInserted = 0;
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(messages.get(0).createdAt);
calendar.setTimeInMillis(filteredList.get(0).createdAt);
if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != calendar.get(Calendar.DAY_OF_YEAR)) {
mDatePositions.add(currentMessagesCount + mLastOffset);
datesInserted++;
mDateList.add(new Date(messages.get(0).createdAt));
mDateList.add(new Date(filteredList.get(0).createdAt));
mPositionOffset.add(0);
mLastOffset++;
}
mPositionOffset.add(mLastOffset);
mDateList.add(new Date(messages.get(0).createdAt));
int messageSize = messages.size();
mDateList.add(new Date(filteredList.get(0).createdAt));
int messageSize = filteredList.size();
for(int i = 1; i < messageSize; i++) {
mCurrentDay.setTimeInMillis(messages.get(i).createdAt);
mLastDay.setTimeInMillis(messages.get(i - 1).createdAt);
mCurrentDay.setTimeInMillis(filteredList.get(i).createdAt);
mLastDay.setTimeInMillis(filteredList.get(i - 1).createdAt);
if(mCurrentDay.get(Calendar.DAY_OF_YEAR) != mLastDay.get(Calendar.DAY_OF_YEAR)) {
mDatePositions.add(currentMessagesCount + mLastOffset + i);
@@ -142,11 +206,21 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
mPositionOffset.add(mLastOffset);
mDateList.add(new Date(messages.get(i).createdAt));
mDateList.add(new Date(filteredList.get(i).createdAt));
}
mMessages.addAll(messages);
notifyItemRangeInserted(currentMessagesCount + lastOffsetBeforeParsing, messages.size() + datesInserted);
if(!isAddingNewerMessages) {
mMessages.addAll(filteredList);
} else {
mMessages.addAll(0, filteredList);
Collections.sort(mMessages, (m1, m2) -> Long.compare(m1.createdAt, m2.createdAt) * -1);
}
if(!isAddingNewerMessages) {
notifyItemRangeInserted(currentMessagesCount + lastOffsetBeforeParsing, filteredList.size() + datesInserted);
} else {
notifyItemRangeInserted(0, filteredList.size() + datesInserted);
}
}
void setItemProgress(int itemPosition, int progress) {
@@ -231,8 +305,6 @@ 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++) {
@@ -242,8 +314,6 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
}
Log.d("MESSAGE_SCROLL", "position: " + position + "; offset: " + mPositionOffset.get(position));
position += mPositionOffset.get(position);
return position;
@@ -304,7 +374,7 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
}
if(myMessage == null) {
Log.e("BaseChatAdapter", "message not found!! " + messageId);
Log.e("BaseChatAdapter", "Message not found! " + messageId);
return;
}
@@ -521,4 +591,12 @@ abstract class BaseChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
void jumpToMessage(Message message);
}
public int getPositionOffsetForIndex(int index) {
if(mPositionOffset.size() == 0 || index < 0) {
return 0;
}
return mPositionOffset.get(index);
}
}

View File

@@ -20,8 +20,10 @@ import de.beyondsoft.ownchat.di.scopes.ActivityScope;
import de.beyondsoft.ownchat.model.Group;
import de.beyondsoft.ownchat.model.Message;
import de.beyondsoft.ownchat.page.mvp.MVPAbstractPresenter;
import de.beyondsoft.ownchat.utils.BeyondSoftLog;
import de.beyondsoft.ownchat.utils.Constants;
import de.beyondsoft.ownchat.utils.SystemUtils;
import de.beyondsoft.ownchat.utils.Utils;
import de.beyondsoft.ownchat.utils.rx.RxUtils;
import org.greenrobot.eventbus.EventBus;
@@ -39,10 +41,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import javax.inject.Inject;
@@ -117,6 +122,20 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
private boolean mFetchMore;
private ArrayList<Message> mMessages = new ArrayList<>();
private ArrayList<Pair<Message, String>> mMessageToUpdate = new ArrayList<>();
private int mDownwardsOffset = -1;
public int getPage() {
return mPage;
}
private boolean mIsScrollingDown = false;
public void setIsScrollingDown(boolean isScrollingDown) {
mIsScrollingDown = isScrollingDown;
}
public boolean getIsScrollingDown() {
return mIsScrollingDown;
}
/**
* Get all messages from the server with a specific groupId.
@@ -251,6 +270,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
mPage = FIRST_PAGE;
mOffset = 0;
mFetchMore = true;
mDownwardsOffset = -1;
}
/**
@@ -316,6 +336,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
messagesByGroupAndSearchTextSpecification.setGroupId(groupId);
messagesByGroupAndSearchTextSpecification.setSearchText(text);
mGroupId = groupId;
mText = text;
mMessageRealmRepository.queryForAll(messagesByGroupAndSearchTextSpecification)
@@ -380,6 +401,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
MessageByGroupIdSpecification messageByGroupIdSpecification = new MessageByGroupIdSpecification(mUserIdPreference.get());
messageByGroupIdSpecification.setGroupId(groupId);
mGroupId = groupId;
mMessageToScrollTo = messageToScrollTo;
mMessageRealmRepository.queryForAll(messageByGroupIdSpecification)
@@ -393,8 +415,6 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
return;
}
Log.w("SCROLLING_LOG" ,"Count of messages loaded: " + messages.size());
mMessages.clear();
mMessages.addAll(messages);
mLoading = false;
@@ -449,7 +469,6 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
storeGroupLocally(mGroupId, message);
}
//mPage++;
mOffset += LIMIT;
getView().scrollToMessage(mMessageToScrollTo);
@@ -476,17 +495,34 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
getView().hideMessagesLoading();
if(!mMessages.isEmpty()) {
int offsetTmp, endOffsetTmp;
int endOffset = mOffset + LIMIT > mMessages.size() ? mMessages.size() : mOffset + LIMIT;
LogFetchingInformation(mOffset, endOffset, LIMIT, mMessages.size());
if(mIsScrollingDown) {
if(mDownwardsOffset < 0) {
mDownwardsOffset = mOffset >= (LIMIT * 2) ? mOffset - (LIMIT * 2) : 0;
} else {
mDownwardsOffset = mDownwardsOffset >= (LIMIT * 2) ? mDownwardsOffset - (LIMIT * 2) : 0;
}
int downwardsEndOffset = mDownwardsOffset + (LIMIT * 2) > mMessages.size() ? mMessages.size() : mDownwardsOffset + (LIMIT * 2);
offsetTmp = mDownwardsOffset;
endOffsetTmp = downwardsEndOffset;
} else {
offsetTmp = mOffset;
endOffsetTmp = endOffset;
}
if(mOffset > endOffset) {
Utils.makeScrollLog("Offset is greater than endOffset: " + mOffset + " > " + endOffset);
return;
}
boolean isFirstPage = mPage == FIRST_PAGE;
getView().messagesLoaded(new ArrayList<>(mMessages.subList(mOffset, endOffset)), isFirstPage);
getView().messagesLoaded(new ArrayList<>(mMessages.subList(offsetTmp, endOffsetTmp)), isFirstPage);
if(isFirstPage && mSystemUtils.isNetworkUnavailable()) {
Message firstMessage = mMessages.get(0);
@@ -509,7 +545,10 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
}
mPage++;
mOffset += LIMIT;
if(!mIsScrollingDown) {
mOffset += LIMIT;
}
return;
}
@@ -903,6 +942,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
checkForEmptyMessageAndHideIt();
message.createdAt *= 1000;
getView().addOneMessage(message);
getView().scrollToPositionZero();
mChatService.sendMessage(mGroupId, text)
.compose(RxUtils.provideDefaultTransformer())
@@ -1071,13 +1111,11 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
.compose(RxUtils.provideDefaultTransformer())
.subscribe(new Subscriber<Group>() {
@Override
public void onCompleted() {
}
public void onCompleted() { }
@Override
public void onError(Throwable e) {
Log.e(ChatPresenter.class.getSimpleName(), e.getMessage(), e);
}
@Override
@@ -1250,19 +1288,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
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));
}
public boolean hasNewerMessages(long groupId, String messageId) {
return mGroupId == groupId && mMessages != null && mMessages.size() != 0 && !mMessages.get(0).id.equals(messageId);
}
}

View File

@@ -58,6 +58,8 @@ interface ChatView extends MVPView {
void scrollToMessage(Message message);
void scrollToPositionZero();
interface Callback {
void loadMessagesInContext(long groupId, long createdAt, Message messageToExclude);
@@ -88,5 +90,11 @@ interface ChatView extends MVPView {
long getGroupId();
void fetchNewMessages(long groupId);
void setIsScrollingDown(boolean isScrollingDown);
boolean getIsScrollingDown();
boolean hasNewerMessages(long groupId, String messageId);
}
}

View File

@@ -11,8 +11,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
public class GroupChatActivity extends BaseChatActivity {
@@ -99,6 +101,9 @@ public class GroupChatActivity extends BaseChatActivity {
if(mGroup == null) {
if(getIntent().hasExtra(MESSAGE_ARG)) {
long groupId = getIntent().getBundleExtra(MESSAGE_ARG).getLong(Constants.GROUP_ID);
mCallback.findGroupById();
mCallback.fetchNewMessages(groupId);
super.onResume();
return;
@@ -115,6 +120,7 @@ public class GroupChatActivity extends BaseChatActivity {
cancelNotification();
IntentFilter filter = new IntentFilter(Constants.NOTIFICATION_BROADCAST);
registerReceiver(mReceiver, filter);
super.onResume();
}
@@ -173,8 +179,16 @@ public class GroupChatActivity extends BaseChatActivity {
@Override
protected void onPause() {
Log.v("GROUP_NOTIFICATION", "onPause() called");
mChatPresenter.detachView();
unregisterReceiver(mReceiver);
try {
unregisterReceiver(mReceiver);
} catch(Exception e) {
Log.e(GroupChatActivity.class.getSimpleName(), e.getMessage(), e);
}
super.onPause();
}
@@ -190,6 +204,8 @@ public class GroupChatActivity extends BaseChatActivity {
@Override
public void setGroup(Group group) {
Log.e("GROUP_NOTIFICATION", "Setting group: " + group);
mGroup = group;
setToolbar();

View File

@@ -16,6 +16,7 @@ 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;

View File

@@ -28,6 +28,7 @@ import de.beyondsoft.ownchat.page.main.MainNavigation;
import de.beyondsoft.ownchat.page.mvp.MVPAbstractPresenter;
import de.beyondsoft.ownchat.utils.SystemUtils;
import de.beyondsoft.ownchat.utils.rx.RxUtils;
import com.securepreferences.SecurePreferences;
import android.app.NotificationManager;
@@ -167,7 +168,8 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
private int mStatus;
@Inject
LoginPresenter() {}
LoginPresenter() {
}
@Override
protected void afterAttachView(@NonNull LoginView view) {
@@ -182,8 +184,10 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
}
//region Login and device registration
/**
* Try to login with the given parameters
*
* @param username login username as {@code String}
* @param password login password as {@code String}
* @param chatCode login chat code as {@code String}
@@ -218,7 +222,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
private void loginError(Throwable throwable) {
getView().showContent();
if (mSystemUtils.isNetworkUnavailable()) {
if(mSystemUtils.isNetworkUnavailable()) {
getView().showError(R.string.error_no_internet_connection);
return;
}
@@ -228,29 +232,29 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
switch(ErrorUtils.getErrorType(throwable)) {
case DATA_INPUT:
if (errorThrowable.getError().loginFailed != null && errorThrowable.getError().loginFailed.size() > 0) {
if(errorThrowable.getError().loginFailed != null && errorThrowable.getError().loginFailed.size() > 0) {
getView().showError(R.string.error_invalid_login_credentials);
return;
}
case WRONG_CHATCODE:
if (errorThrowable.getError().chatCodeFailed != null && errorThrowable.getError().chatCodeFailed.size() > 0) {
if(errorThrowable.getError().chatCodeFailed != null && errorThrowable.getError().chatCodeFailed.size() > 0) {
getView().showError(R.string.error_wrong_chat_code);
return;
}
case DEACTIVATED_CHATCODE:
if (errorThrowable.getError().chatCodeInactive != null && errorThrowable.getError().chatCodeInactive.size() > 0) {
if(errorThrowable.getError().chatCodeInactive != null && errorThrowable.getError().chatCodeInactive.size() > 0) {
getView().showError(R.string.error_deactivated_chat_code);
return;
}
case CHATCODE_TOO_SHORT:
if (errorThrowable.getError().chatCodeTooShort != null && errorThrowable.getError().chatCodeTooShort.size() > 0) {
if(errorThrowable.getError().chatCodeTooShort != null && errorThrowable.getError().chatCodeTooShort.size() > 0) {
getView().showError(R.string.error_chat_code_too_short);
return;
}
default:
getView().showError(R.string.error_something_went_wrong);
}
} catch (Exception e) {
} catch(Exception e) {
getView().showError(R.string.login_service_type_is_undefined);
e.printStackTrace();
}
@@ -258,6 +262,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
/**
* Set user data in preferences if login is successful.
*
* @param user {@code User} the current logged in user.
*/
private void loginSuccess(User user) {
@@ -280,16 +285,16 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
}
/**
*Checks the {@code String} username and {@code String} password
* Checks the {@code String} username and {@code String} password
* if are corresponding to criteria.
*/
@Override
public void tryToLogin(String username, String password, String chatCode) {
if (!checkPassword(password)) {
if(!checkPassword(password)) {
return;
}
if (!checkUsername(username)) {
if(!checkUsername(username)) {
return;
}
@@ -311,15 +316,15 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
}
private void registerError(Throwable throwable) {
if (ErrorUtils.getErrorType(throwable) == ErrorType.DATA_INPUT) {
if(ErrorUtils.getErrorType(throwable) == ErrorType.DATA_INPUT) {
ErrorThrowable errorThrowable = (ErrorThrowable) throwable;
if (errorThrowable.getError().fcmToken != null && errorThrowable.getError().fcmToken.size() > 0) {
if(errorThrowable.getError().fcmToken != null && errorThrowable.getError().fcmToken.size() > 0) {
Log.d(TAG, "fcmToken error: " + errorThrowable.getError().fcmToken.get(0));
return;
}
if (errorThrowable.getError().deviceId != null && errorThrowable.getError().deviceId.size() > 0) {
if(errorThrowable.getError().deviceId != null && errorThrowable.getError().deviceId.size() > 0) {
Log.d(TAG, "fcmToken error: " + errorThrowable.getError().deviceId.get(0));
}
}
@@ -330,6 +335,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
//endregion
//region CheckTypeAndCustomerId
/**
* Checks if the voucher necessary for login is correct.
*/
@@ -340,11 +346,11 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
mAccountNumberPreference.set(customerId);
mSavePasswordPreference.set(isChecked);
if (!checkPassword(password)) {
if(!checkPassword(password)) {
return;
}
if (TextUtils.isEmpty(customerId)) {
if(TextUtils.isEmpty(customerId)) {
getView().showError(R.string.error_customer_id_must_not_be_null);
return;
}
@@ -355,7 +361,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
.flatMap(response -> {
JsonObject serviceObject = response.getAsJsonObject();
if (serviceObject.has("Status")
if(serviceObject.has("Status")
&& !TextUtils.isEmpty(serviceObject.get("Status").getAsString())
&& serviceObject.has("URL")
&& !TextUtils.isEmpty(serviceObject.get("URL").getAsString())) {
@@ -363,7 +369,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
String serverName = serviceObject.get("URL").getAsString();
if (!serverName.startsWith("https")) {
if(!serverName.startsWith("https")) {
serverName = "https://" + serverName + "/";
}
@@ -430,7 +436,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
private void retrieveUploadMaxSizeError(Throwable throwable) {
getView().showContent();
if (mSystemUtils.isNetworkUnavailable()) {
if(mSystemUtils.isNetworkUnavailable()) {
getView().showError(R.string.error_no_internet_connection);
return;
}
@@ -446,6 +452,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
//endregion
//region Logout
/**
* Logout from application deletes all login preferences and redirects to login screen.
*/
@@ -453,8 +460,8 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
public void logout(String deviceId, boolean clearAllPrefs, MainNavigation mainNavigation) {
mNotificationManager.cancelAll();
if (!mLoginTokenPreference.isSet()) {
if (mainNavigation != null) {
if(!mLoginTokenPreference.isSet()) {
if(mainNavigation != null) {
mainNavigation.goToLoginActivity();
}
@@ -471,7 +478,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
mLoginTokenPreference.delete();
deleteData(clearAllPrefs);
if (mainNavigation != null) {
if(mainNavigation != null) {
mainNavigation.goToLoginActivity();
}
}
@@ -483,7 +490,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
mLoginTokenPreference.delete();
deleteData(clearAllPrefs);
if (mainNavigation != null) {
if(mainNavigation != null) {
mainNavigation.goToLoginActivity();
}
}
@@ -497,7 +504,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
@Override
public void tryToLogout(String deviceId, MainNavigation mainNavigation) {
if (mSavePasswordPreference.get()) {
if(mSavePasswordPreference.get()) {
return;
}
@@ -507,7 +514,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
@Override
public void verifyChatCode(LogoutNavigation logoutNavigation) {
if (mAccountNumberPreference.get() == null) {
if(mAccountNumberPreference.get() == null) {
return;
}
@@ -519,11 +526,12 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
.compose(RxUtils.provideDefaultTransformer())
.subscribe(new Subscriber<JsonObject>() {
@Override
public void onCompleted() {}
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
if (mSystemUtils.isNetworkUnavailable() || e instanceof SocketTimeoutException) {
if(mSystemUtils.isNetworkUnavailable() || e instanceof SocketTimeoutException) {
return;
}
@@ -532,8 +540,8 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
@Override
public void onNext(JsonObject response) {
if (!response.has("result") || response.get("result") == null) {
getView().showError("Test message after verifying the cat code");
if(!response.has("result") || response.get("result") == null) {
getView().showError("Test message after verifying the chat code");
logoutNavigation.logoutDirectly(false);
return;
}
@@ -564,7 +572,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
* Clears all login preferences.
*/
private void deleteData(boolean clearAllPrefs) {
if (clearAllPrefs) {
if(clearAllPrefs) {
Observable.create(subscriber -> mGlide.clearDiskCache())
.subscribeOn(Schedulers.io())
.subscribe();
@@ -577,11 +585,11 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
File dir = mContext.getExternalFilesDir(null);
if (dir != null) {
if (dir.isDirectory()) {
if(dir != null) {
if(dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
for(String aChildren : children) {
//noinspection ResultOfMethodCallIgnored
new File(dir, aChildren).delete();
}
@@ -591,10 +599,10 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
} else {
dir = new File(Environment.getExternalStorageDirectory(), "BeWoPlaner");
if (dir.isDirectory()) {
if(dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
for(String aChildren : children) {
//noinspection ResultOfMethodCallIgnored
new File(dir, aChildren).delete();
}
@@ -614,11 +622,11 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
File dir = mContext.getExternalFilesDir(null);
mGlide.clearMemory();
if (dir != null) {
if (dir.isDirectory()) {
if(dir != null) {
if(dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
for(String aChildren : children) {
//noinspection ResultOfMethodCallIgnored
new File(dir, aChildren).delete();
}
@@ -628,10 +636,10 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
} else {
dir = new File(Environment.getExternalStorageDirectory(), "BeWoPlaner");
if (dir.isDirectory()) {
if(dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
for(String aChildren : children) {
//noinspection ResultOfMethodCallIgnored
new File(dir, aChildren).delete();
}
@@ -645,7 +653,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
}
private boolean checkPassword(String password) {
if (password.length() >= 1) {
if(password.length() >= 1) {
return true;
}
@@ -654,7 +662,7 @@ public class LoginPresenter extends MVPAbstractPresenter<LoginView> implements L
}
private boolean checkUsername(String username) {
if (username.length() >= 1) {
if(username.length() >= 1) {
return true;
}

View File

@@ -0,0 +1,27 @@
package de.beyondsoft.ownchat.utils;
import android.util.Log;
public class BeyondSoftLog {
private static String SCROLLING_LOG_TAG = "SCROLLING_LOG";
public static void logScrollingEvent_D(String message) {
Log.d(SCROLLING_LOG_TAG, message);
}
public static void logScrollingEvent_I(String message) {
Log.i(SCROLLING_LOG_TAG, message);
}
public static void logScrollingEvent_W(String message) {
Log.w(SCROLLING_LOG_TAG, message);
}
public static void logScrollingEvent_E(String message) {
Log.e(SCROLLING_LOG_TAG, message);
}
public static void logScrollingEvent_E(String message, Throwable throwable) {
Log.e(SCROLLING_LOG_TAG, message, throwable);
}
}

View File

@@ -41,19 +41,11 @@ public interface Constants {
String LOG_TAG_NETWORK_INTERCEPTOR = "NETWORK_INTERCEPTOR";
String LOG_TAG_FILE_PICKER = "FILE_PICKER";
String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq2g5mGlWyikJyrHeiB/j" +
"u9oNJCkJgqWTu03g47wgTqbIkwrgTZsbh5QmBztEs/UPHwawRC768gHRBmQLf+nC" +
"jMwCWHTE/xzYgr1xD+4Hd3Owkd1Ko3dnIK2ihUjWSEaepYUMDMNF5FHlTFoKWmHe" +
"7akO0EmUqL63kKoQad78MCg6cORu01dr1CWbIxN9fc8c06X8K+hWvIrgA/1U7C22" +
"y0UYUQnwiQpD8WQSztyal58eyqkFSzWA3SZfyOaxSpnnGpge5VEfNhkG5Ufkzqk1" +
"NoP+R0w3rFsPhw6/cH+++6LOPFQyZ/VkbmkFtUkjOrF0lmoHZUywksKO3ag8/2Tx" +
"NQIDAQAB";
String DICTIONARY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAttMkndZO0+8idOJj61w5" +
"iFJkPTkLeDSDe0b0g+f8fdSFjT6rRHV3PGuXMdxDEa1NAustGd23vju+gMegNL13" +
"SC/hLi3lWscl2QB/4Inkm3vXf8YtnrYlkUV4MGAk4BmHyydREOi6xHKF05X9yALA" +
"wCGtsXALn+tjC9mpaM9h6mI3cdfQiOkaQ0fYkuULs1F9MuVwblBw/d+0LWfZFKjj" +
"K/Y8MmR5o9JdpzXtUh+6g98nfa7HN2iEamQiQYH5VhRNCl/LKnyCkH1DAc4oQ237" +
"+X21F2WnGDTkEW0sX1BmiCi8EKz5JtgjNVvPgOS76hskQHhlSWQM177b6EtXn47Q" +
"IQIDAQAB";
String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7No" +
"YzDq1zUmGSXhvb418XCSL7e4S0EFq6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf" +
"15Gan/PQeGdxyGkOlZHP/uaZ6WA8SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K" +
"671B5TaYucv9bTyWaN8jKkKQDIZ0Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+X" +
"hq+w3Brvaw2VFn3EK6BlspkENnWAa6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiV" +
"FrqoM7hD8bEfwzB/onkxEz0tNvjj/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3D" +
"kwIDAQAB";
}

View File

@@ -2,7 +2,7 @@ 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 {
@@ -11,6 +11,10 @@ public abstract class EndlessScrollListener extends RecyclerView.OnScrollListene
private boolean mLoading = false;
private final LinearLayoutManager mLayoutManager;
private int mVisibleItem;
protected boolean mHasNewerMessages = false;
public int mPreviousVisibleItem;
public boolean mIsScrollingDownLock;
public void reset() {
mPreviousTotal = 0;
@@ -38,11 +42,26 @@ 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();
try {
//BaseChatAdapter adapter = (BaseChatAdapter) recyclerView.getAdapter();
//mVisibleItem -= adapter.getPositionOffsetForIndex(mVisibleItem);
if(!mLoading && dy <= 0 && ((totalItemCount - visibleItemCount) <= (mVisibleItem + mVisibleThreshold))) {
mLoading = onLoad();
} else if(!mIsScrollingDownLock && !mLoading && dy > 0 && mHasNewerMessages && (mVisibleItem <= (mVisibleThreshold / 5))) {
// TODO: maybe remove after structural changes to the messages list
// mPreviousVisibleItem = mVisibleItem + adapter.getPositionOffsetForIndex(mVisibleItem);
// mIsScrollingDownLock = true;
// TODO -----------------------------------------------------------
mLoading = onLoad();
}
} catch(Exception e) {
BeyondSoftLog.logScrollingEvent_E(e.getMessage(), e);
e.printStackTrace();
}
});
}

View File

@@ -3,6 +3,7 @@ package de.beyondsoft.ownchat.utils;
import android.app.Activity;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
@@ -182,4 +183,8 @@ public class Utils {
return result;
}
public static void makeScrollLog(String message) {
Log.i("SCROLLING_LOG", message);
}
}

View File

@@ -19,7 +19,8 @@
android:layout_marginBottom="56dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="?attr/actionBarSize"
android:layout_marginTop="?android:attr/actionBarSize"
android:descendantFocusability="blocksDescendants"
tools:listitem="@layout/item_single_chat_left" />
<de.beyondsoft.ownchat.utils.ui.EmptyLayout

View File

@@ -5,7 +5,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/chat_app_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_height="?android:attr/actionBarSize"
android:background="@color/colorWhite"
tools:context=".page.chat.BaseChatActivity">
@@ -46,13 +46,33 @@
android:visibility="gone"
tools:text="Sample title" />
<!-- TODO: Activated search view
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/chat_toolbar_image_view"
android:layout_toStartOf="@+id/chatSearchView"
android:layout_marginEnd="-45dp"
android:orientation="vertical">
TODO: Deactivated search view
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/chat_toolbar_image_view"
android:layout_toStartOf="@+id/chat_toolbar_extra_image"
android:orientation="vertical">
-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="-45dp"
android:layout_toEndOf="@+id/chat_toolbar_image_view"
android:layout_toStartOf="@+id/chatSearchView"
android:layout_marginEnd="-45dp"
android:orientation="vertical">
<TextView
@@ -64,7 +84,8 @@
android:textSize="15sp"
android:textStyle="bold"
android:visibility="gone"
tools:text="Group title" />
tools:text="Gruppenname"
tools:visibility="visible"/>
<TextView
android:id="@+id/chat_toolbar_group_members"
@@ -77,7 +98,8 @@
android:textSize="11sp"
android:visibility="gone"
tools:ignore="SmallSp"
tools:text="Group title" />
tools:text="Lyndon Jetten, Sonja Estevao, Jörg Behr, Christian Bäurle, Markus Bäurle, Mike Gahn"
tools:visibility="visible"/>
</LinearLayout>
<android.support.v7.widget.SearchView
@@ -92,7 +114,9 @@
android:paddingTop="5dp"
app:iconifiedByDefault="true"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:theme="@style/AppTheme.Toolbar" />
app:theme="@style/AppTheme.Toolbar"
android:visibility="visible"
/>
<ImageView
android:id="@+id/chat_toolbar_extra_image"

View File

@@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'io.realm:realm-gradle-plugin:5.3.0'
// NOTE: Do not place your application dependencies here; they belong