Recording audio file is now supported.
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "de.beyondsoft.ownchat"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 26
|
||||
versionCode 6
|
||||
versionName "1.6"
|
||||
versionCode 7
|
||||
versionName "1.7"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ dependencies {
|
||||
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||
compile 'de.hdodenhof:circleimageview:2.1.0'
|
||||
compile 'com.scottyab:secure-preferences-lib:0.1.4'
|
||||
compile 'com.google.firebase:firebase-messaging:11.4.2'
|
||||
compile 'com.google.firebase:firebase-messaging:11.6.0'
|
||||
compile 'hani.momanii.supernova_emoji_library:supernova-emoji-library:0.0.2'
|
||||
compile 'com.github.lzyzsd:circleprogress:1.2.1'
|
||||
compile 'org.greenrobot:eventbus:3.0.0'
|
||||
@@ -136,4 +136,4 @@ task adp2upload(type: Exec, dependsOn: "build") {
|
||||
commandLine 'upload_build.sh', "-c", "$channel", "-v", "$versionCode", "-p", "android", "-a", "$slug", "-f", "$file", "-i", "$identifier", "-n", "$notes"
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
@@ -68,6 +68,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@@ -347,7 +348,6 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
|
||||
private int xDelta;
|
||||
private String mAudioFilePath;
|
||||
private Rect mRecButtonRightLimit;
|
||||
private int mMaxMarginRight;
|
||||
private Rect mDialogContainer;
|
||||
private void selectFile(int requestCode) {
|
||||
@@ -368,6 +368,7 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
ImageView attachDocumentImageView = dialog.findViewById(R.id.attachDocumentBtn);
|
||||
TouchableImageView attachRecordAudioImageView = dialog.findViewById(R.id.attachRecordAudioBtn);
|
||||
TextView limitTextView = dialog.findViewById(R.id.audioRecordingLimitTextView);
|
||||
ImageView abortRecordingImageView = dialog.findViewById(R.id.abortRecordingImageView);
|
||||
|
||||
attachDocumentImageView.setOnClickListener(v -> {
|
||||
FilePickerBuilder.getInstance().setMaxCount(1)
|
||||
@@ -395,16 +396,20 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
});
|
||||
|
||||
dialog.setOnShowListener(dialog1 -> {
|
||||
mRecButtonRightLimit = ViewUtils.getRectFromView(attachRecordAudioImageView);
|
||||
mMaxMarginRight = ((GridLayout.LayoutParams) attachRecordAudioImageView.getLayoutParams()).leftMargin;
|
||||
mDialogContainer = ViewUtils.getRectFromView(dialog.findViewById(R.id.dialogGridLayout));
|
||||
});
|
||||
|
||||
final long duration = (mUploadMaxSize.get() / 256000L) * 1000;
|
||||
Log.d("ON_TOUCH", "Upload max size: " + mUploadMaxSize.get());
|
||||
|
||||
final long duration = ((mUploadMaxSize.get() / 10 * 9) * 8) / (Constants.RECORDING_BIT_RATE_IN_MS / 1000); // 10% Overhead
|
||||
CountDownTimer countDownTimer = new CountDownTimer(duration, 10) {
|
||||
@Override
|
||||
public void onTick(long millisecondsUntilFinished) {
|
||||
limitTextView.setText(String.format(Locale.getDefault(), "%.2fs", (double) (millisecondsUntilFinished / 1000.0)));
|
||||
long minutes = TimeUnit.MILLISECONDS.toMinutes(millisecondsUntilFinished);
|
||||
long seconds = TimeUnit.MILLISECONDS.toSeconds(millisecondsUntilFinished) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisecondsUntilFinished));
|
||||
|
||||
limitTextView.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -416,19 +421,19 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
attachFromGalleryImageView.setVisibility(View.VISIBLE);
|
||||
|
||||
try {
|
||||
mMediaRecorder.stop();
|
||||
mMediaRecorder.stop(); // TODO: delete
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: prevent the image view from moving outside the dialog box
|
||||
attachRecordAudioImageView.setOnTouchListener((v, event) -> {
|
||||
try {
|
||||
attachRecordAudioImageView.performClick();
|
||||
|
||||
final int x = (int) event.getRawX();
|
||||
int test = 0;
|
||||
|
||||
switch(event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
@@ -439,6 +444,7 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
limitTextView.setVisibility(View.VISIBLE);
|
||||
attachDocumentImageView.setVisibility(View.INVISIBLE);
|
||||
attachFromGalleryImageView.setVisibility(View.INVISIBLE);
|
||||
abortRecordingImageView.setVisibility(View.VISIBLE);
|
||||
|
||||
mAudioFilePath = FileUtils.generateAudioRecordingFileName(this);
|
||||
setupMediaRecorder(mAudioFilePath);
|
||||
@@ -461,11 +467,34 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
|
||||
mMediaRecorder.stop();
|
||||
|
||||
Rect recordButtonRect = ViewUtils.getRectFromView(attachRecordAudioImageView);
|
||||
Rect abortButtonRect = ViewUtils.getRectFromView(abortRecordingImageView);
|
||||
|
||||
if(recordButtonRect.intersect(abortButtonRect)) {
|
||||
if(!TextUtils.isEmpty(mAudioFilePath)) {
|
||||
File audioFile = new File(mAudioFilePath);
|
||||
if(audioFile.exists()) {
|
||||
audioFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mChatPresenter.isAttached()) {
|
||||
mChatPresenter.attachView(this);
|
||||
}
|
||||
|
||||
//mCallback.sendFile(Uri.fromFile(new File(mAudioFilePath)), null);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.FILENAME, mAudioFilePath);
|
||||
|
||||
Intent intent = new Intent(this, FileConfirmationActivity.class);
|
||||
intent.putExtras(bundle);
|
||||
|
||||
intent.putExtra(Constants.EXTRA_GROUP, mGroup);
|
||||
|
||||
startActivityForResult(intent, 666);
|
||||
|
||||
dialog.dismiss();
|
||||
return true;
|
||||
@@ -475,9 +504,11 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
|
||||
int newLeft = x - xDelta;
|
||||
|
||||
Log.d("ON_TOUCH", "dialog container left: " + mDialogContainer.left + "; x: " + x + "; limit to the right: " + mMaxMarginRight + "; old margin left: " + layoutParams2.leftMargin);
|
||||
Log.d("ON_TOUCH", "new left margin: " + newLeft);
|
||||
|
||||
if(x > mDialogContainer.left && newLeft < mMaxMarginRight) {
|
||||
int buttonLeft = ViewUtils.getRectFromView(attachRecordAudioImageView).left;
|
||||
|
||||
if(buttonLeft > mDialogContainer.left && newLeft < mMaxMarginRight) {
|
||||
layoutParams2.leftMargin = newLeft;
|
||||
layoutParams2.rightMargin = 0;
|
||||
v.setLayoutParams(layoutParams2);
|
||||
@@ -501,7 +532,7 @@ abstract class BaseChatActivity extends RuntimePermissionActivity implements Cha
|
||||
mMediaRecorder = new MediaRecorder();
|
||||
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
||||
mMediaRecorder.setAudioEncodingBitRate(32000);
|
||||
mMediaRecorder.setAudioEncodingBitRate(Constants.RECORDING_BIT_RATE_IN_MS);
|
||||
mMediaRecorder.setAudioEncoder(MediaRecorder.OutputFormat.AMR_NB);
|
||||
mMediaRecorder.setOutputFile(audioFilePath);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import de.beyondsoft.ownchat.R;
|
||||
import de.beyondsoft.ownchat.data.AppPrefsConstants;
|
||||
import de.beyondsoft.ownchat.data.api.common.ErrorHandlingResponseConverter;
|
||||
import de.beyondsoft.ownchat.data.api.responses.MessageResponse;
|
||||
import de.beyondsoft.ownchat.data.api.responses.MessagesResponse;
|
||||
import de.beyondsoft.ownchat.data.api.services.ChatService;
|
||||
import de.beyondsoft.ownchat.data.events.GroupUpdatedEvent;
|
||||
import de.beyondsoft.ownchat.data.prefs.LongPreference;
|
||||
@@ -27,15 +26,12 @@ import de.beyondsoft.ownchat.utils.rx.RxUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -43,6 +39,7 @@ import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
@@ -59,7 +56,6 @@ import rx.Observable;
|
||||
import rx.Subscriber;
|
||||
import rx.Subscription;
|
||||
import rx.android.schedulers.AndroidSchedulers;
|
||||
import rx.functions.Func1;
|
||||
import rx.schedulers.Schedulers;
|
||||
|
||||
import static de.beyondsoft.ownchat.data.AppPrefsConstants.USER_ID;
|
||||
@@ -165,51 +161,48 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
|
||||
Subscription subscription = mChatService.getMessages(mGroupId, 0)
|
||||
.compose(RxUtils.provideDefaultTransformer())
|
||||
.flatMap(new ErrorHandlingResponseConverter<>())
|
||||
.flatMap(new Func1<MessagesResponse, Observable<List<Message>>>() {
|
||||
@Override
|
||||
public Observable<List<Message>> call(MessagesResponse messageResponse) {
|
||||
if (messageResponse.response.messages == null || messageResponse.response.messages.isEmpty()) {
|
||||
return Observable.just(null);
|
||||
}
|
||||
|
||||
Message lastMessage = messageResponse.response.messages.get(messageResponse.response.messages.size() - 1);
|
||||
lastMessage.status = Message.Status.DELIVERED;
|
||||
lastMessage.isRead = true;
|
||||
|
||||
if (!mMessages.contains(lastMessage)) {
|
||||
long groupId = mGroupId;
|
||||
resetMessages();
|
||||
loadMessagesFromServerByGroupId(groupId);
|
||||
|
||||
return Observable.just(null);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mMessageToUpdate.size(); i++) {
|
||||
Pair<Message, String> messageToUpdate = mMessageToUpdate.get(i);
|
||||
|
||||
if (isAttached()) {
|
||||
getView().lastMessageUpdated(messageToUpdate.first, messageToUpdate.second);
|
||||
}
|
||||
}
|
||||
|
||||
mMessageToUpdate.clear();
|
||||
ArrayList<Message> messages = new ArrayList<>();
|
||||
|
||||
for (int i = messageResponse.response.messages.size() - 1; i >= 0; i--) {
|
||||
Message message = messageResponse.response.messages.get(i);
|
||||
message.status = Message.Status.DELIVERED;
|
||||
message.isRead = true;
|
||||
message.userId = mUserIdPreference.get();
|
||||
|
||||
if (mMessages.contains(message)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
messages.add(message);
|
||||
}
|
||||
|
||||
return mMessageRealmRepository.updateMessages(messages, new MessageByIdSpecification(mUserIdPreference.get()));
|
||||
.flatMap(messageResponse -> {
|
||||
if (messageResponse.response.messages == null || messageResponse.response.messages.isEmpty()) {
|
||||
return Observable.just(null);
|
||||
}
|
||||
|
||||
Message lastMessage = messageResponse.response.messages.get(messageResponse.response.messages.size() - 1);
|
||||
lastMessage.status = Message.Status.DELIVERED;
|
||||
lastMessage.isRead = true;
|
||||
|
||||
if (!mMessages.contains(lastMessage)) {
|
||||
long groupId1 = mGroupId;
|
||||
resetMessages();
|
||||
loadMessagesFromServerByGroupId(groupId1);
|
||||
|
||||
return Observable.just(null);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mMessageToUpdate.size(); i++) {
|
||||
Pair<Message, String> messageToUpdate = mMessageToUpdate.get(i);
|
||||
|
||||
if (isAttached()) {
|
||||
getView().lastMessageUpdated(messageToUpdate.first, messageToUpdate.second);
|
||||
}
|
||||
}
|
||||
|
||||
mMessageToUpdate.clear();
|
||||
ArrayList<Message> messages = new ArrayList<>();
|
||||
|
||||
for (int i = messageResponse.response.messages.size() - 1; i >= 0; i--) {
|
||||
Message message = messageResponse.response.messages.get(i);
|
||||
message.status = Message.Status.DELIVERED;
|
||||
message.isRead = true;
|
||||
message.userId = mUserIdPreference.get();
|
||||
|
||||
if (mMessages.contains(message)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
messages.add(message);
|
||||
}
|
||||
|
||||
return mMessageRealmRepository.updateMessages(messages, new MessageByIdSpecification(mUserIdPreference.get()));
|
||||
})
|
||||
.subscribe(this::fetchNewMessagesSuccess, this::fetchNewMessagesError);
|
||||
addSubscription(subscription);
|
||||
@@ -418,7 +411,7 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
|
||||
* Send the file to the server.
|
||||
*/
|
||||
@Override
|
||||
public void sendFile(Uri fileUri, String messageText) {
|
||||
public void sendFile(final Uri fileUri, String messageText) {
|
||||
File file = getView().getFile(fileUri);
|
||||
|
||||
final String mimeType = FileUtils.getMimeType(file);
|
||||
@@ -506,11 +499,11 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
|
||||
storeMessageLocally(newMessage, message.id, false);
|
||||
storeGroupLocally(mGroupId, message);
|
||||
|
||||
if (!isAttached()) {
|
||||
if(!isAttached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSystemUtils.isNetworkUnavailable()) {
|
||||
if(mSystemUtils.isNetworkUnavailable()) {
|
||||
getView().showError(R.string.error_no_internet_connection);
|
||||
return;
|
||||
}
|
||||
@@ -526,6 +519,15 @@ public class ChatPresenter extends MVPAbstractPresenter<ChatView> implements Cha
|
||||
baseResponse.response.message.isRead = true;
|
||||
storeMessageLocally(baseResponse.response.message, message.id, false);
|
||||
storeGroupLocally(mGroupId, baseResponse.response.message);
|
||||
|
||||
List<String> splitUriAsList = Arrays.asList(fileUri.toString().split("/"));
|
||||
|
||||
if(splitUriAsList.contains(Constants.AUDIO_RECORDINGS_DIR)) {
|
||||
File f = getView().getFile(fileUri);
|
||||
if(f != null && f.exists()) {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,4 +36,6 @@ public interface Constants {
|
||||
String HAS_OPEN_CHAT = "has_open_chat";
|
||||
|
||||
String AUDIO_RECORDINGS_DIR = "audio_recordings";
|
||||
|
||||
int RECORDING_BIT_RATE_IN_MS = 32000;
|
||||
}
|
||||
@@ -56,17 +56,6 @@ public abstract class ViewUtils {
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
public static boolean isInViewInBounds(View view, int x, int y) {
|
||||
Rect outRect = new Rect();
|
||||
int[] location = new int[2];
|
||||
|
||||
view.getDrawingRect(outRect);
|
||||
view.getLocationOnScreen(location);
|
||||
outRect.offset(location[0], location[1]);
|
||||
|
||||
return outRect.contains(x, y);
|
||||
}
|
||||
|
||||
public static Rect getRectFromView(View view) {
|
||||
Rect outRect = new Rect();
|
||||
int[] location = new int[2];
|
||||
@@ -78,4 +67,16 @@ public abstract class ViewUtils {
|
||||
|
||||
return outRect;
|
||||
}
|
||||
|
||||
public static Rect getHitRectFromView(View view) {
|
||||
Rect outRect = new Rect();
|
||||
int[] location = new int[2];
|
||||
|
||||
view.getHitRect(outRect);
|
||||
view.getLocationOnScreen(location);
|
||||
|
||||
outRect.offset(location[0], location[1]);
|
||||
|
||||
return outRect;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/dialogGridLayout"
|
||||
android:layout_width="200dp"
|
||||
android:layout_width="203dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="false"
|
||||
@@ -14,14 +14,28 @@
|
||||
android:orientation="horizontal"
|
||||
android:rowCount="1">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/abortRecordingImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_column="0"
|
||||
android:layout_gravity="center_vertical|center_horizontal"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_row="0"
|
||||
android:adjustViewBounds="false"
|
||||
android:contentDescription="@null"
|
||||
android:cropToPadding="false"
|
||||
android:src="@mipmap/ic_trash_bin"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/audioRecordingLimitTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_column="0"
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="center_vertical|center_horizontal"
|
||||
android:layout_margin="20dp"
|
||||
android:layout_column="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_row="0"
|
||||
android:textAlignment="center"
|
||||
android:textColor="@color/colorBlack"
|
||||
|
||||
|
After Width: | Height: | Size: 781 B |
|
After Width: | Height: | Size: 250 B |
|
After Width: | Height: | Size: 343 B |
|
After Width: | Height: | Size: 515 B |
|
After Width: | Height: | Size: 252 B |
|
After Width: | Height: | Size: 289 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 391 B |
|
After Width: | Height: | Size: 417 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 550 B |
|
After Width: | Height: | Size: 619 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 892 B |
|
After Width: | Height: | Size: 865 B |