diff --git a/app/build.gradle b/app/build.gradle
index ec57c308..1abe8502 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -23,8 +23,8 @@ ext.versions = [
]
def versionMajor = 0
-def versionMinor = 5
-def versionPatch = 18
+def versionMinor = 6
+def versionPatch = 2
android {
compileSdkVersion versions.targetSdkVersion
@@ -59,9 +59,9 @@ android {
buildTypes {
release {
- minifyEnabled false
- shrinkResources false
- useProguard false
+ minifyEnabled true
+ shrinkResources true
+ useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
@@ -98,9 +98,9 @@ android {
}
}
-//repositories {
-// maven { url 'https://maven.fabric.io/public' }
-//}
+repositories {
+ maven { url 'https://maven.fabric.io/public' }
+}
// Remove not needed buildVariants.
android.variantFilter { variant ->
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index f1b42451..80ef6edc 100755
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -19,3 +19,5 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+
+-keep class com.dimowner.audiorecorder.** { *; }
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 958bf45e..92c10ec6 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -39,13 +39,15 @@
android:screenOrientation="portrait"/>
+
-
+
diff --git a/app/src/main/java/com/dimowner/audiorecorder/AppConstants.java b/app/src/main/java/com/dimowner/audiorecorder/AppConstants.java
index 0fa3b78c..8b24cd1c 100755
--- a/app/src/main/java/com/dimowner/audiorecorder/AppConstants.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/AppConstants.java
@@ -36,6 +36,8 @@ private AppConstants() {}
public static final int RECORDING_FORMAT_M4A = 0;
public static final int RECORDING_FORMAT_WAV = 1;
+ public static final int DEFAULT_PER_PAGE = 50;
+
//BEGINNING-------------- Waveform visualisation constants ----------------------------------
/** Density pixel count per one second of time.
@@ -73,6 +75,10 @@ private AppConstants() {}
public final static int RECORD_ENCODING_BITRATE_128000 = 128000;
public final static int RECORD_ENCODING_BITRATE_192000 = 192000;
+ public static final int SORT_DATE = 1;
+ public static final int SORT_NAME = 2;
+ public static final int SORT_DURATION = 3;
+
// public final static int RECORD_AUDIO_CHANNELS_COUNT = 2;
public final static int RECORD_AUDIO_MONO = 1;
public final static int RECORD_AUDIO_STEREO = 2;
diff --git a/app/src/main/java/com/dimowner/audiorecorder/Contract.java b/app/src/main/java/com/dimowner/audiorecorder/Contract.java
index 3bf56ec5..60f962b0 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/Contract.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/Contract.java
@@ -26,6 +26,8 @@ interface View {
void showError(String message);
void showError(int resId);
+
+ void showMessage(int resId);
}
interface UserActionsListener {
diff --git a/app/src/main/java/com/dimowner/audiorecorder/Injector.java b/app/src/main/java/com/dimowner/audiorecorder/Injector.java
index c2559ab8..d1eeb060 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/Injector.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/Injector.java
@@ -47,6 +47,7 @@ public class Injector {
private BackgroundQueue recordingTasks;
private BackgroundQueue importTasks;
private BackgroundQueue processingTasks;
+ private BackgroundQueue copyTasks;
private MainContract.UserActionsListener mainPresenter;
private RecordsContract.UserActionsListener recordsPresenter;
@@ -105,6 +106,13 @@ public BackgroundQueue provideProcessingTasksQueue() {
return processingTasks;
}
+ public BackgroundQueue provideCopyTasksQueue() {
+ if (copyTasks == null) {
+ copyTasks = new BackgroundQueue("CopyTasks");
+ }
+ return copyTasks;
+ }
+
public ColorMap provideColorMap() {
return ColorMap.getInstance(providePrefs());
}
@@ -133,8 +141,8 @@ public MainContract.UserActionsListener provideMainPresenter() {
public RecordsContract.UserActionsListener provideRecordsPresenter() {
if (recordsPresenter == null) {
recordsPresenter = new RecordsPresenter(provideLocalRepository(), provideFileRepository(),
- provideLoadingTasksQueue(), provideRecordingTasksQueue(), provideAudioPlayer(),
- provideAppRecorder(), providePrefs());
+ provideLoadingTasksQueue(), provideRecordingTasksQueue(), provideCopyTasksQueue(),
+ provideAudioPlayer(), provideAppRecorder(), providePrefs());
}
return recordsPresenter;
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/AppRecorderImpl.java b/app/src/main/java/com/dimowner/audiorecorder/app/AppRecorderImpl.java
index b6857744..38c87db4 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/AppRecorderImpl.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/AppRecorderImpl.java
@@ -7,12 +7,12 @@
import com.dimowner.audiorecorder.data.Prefs;
import com.dimowner.audiorecorder.data.database.LocalRepository;
import com.dimowner.audiorecorder.exception.AppException;
+import com.dimowner.audiorecorder.exception.CantProcessRecord;
import com.dimowner.audiorecorder.util.AndroidUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import timber.log.Timber;
@@ -113,7 +113,12 @@ public void run() {
onRecordFinishProcessing();
}
});
- } catch (IOException e) {
+ } catch (IOException | OutOfMemoryError e) {
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override public void run() {
+ onError(new CantProcessRecord());
+ }
+ });
Timber.e(e);
}
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/info/ActivityInformation.java b/app/src/main/java/com/dimowner/audiorecorder/app/info/ActivityInformation.java
new file mode 100644
index 00000000..a6e7d9eb
--- /dev/null
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/info/ActivityInformation.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2019 Dmitriy Ponomarenko
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.dimowner.audiorecorder.app.info;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.dimowner.audiorecorder.ARApplication;
+import com.dimowner.audiorecorder.ColorMap;
+import com.dimowner.audiorecorder.R;
+import com.dimowner.audiorecorder.util.AndroidUtils;
+import com.dimowner.audiorecorder.util.TimeUtils;
+
+public class ActivityInformation extends Activity {
+
+ private static final String KEY_NAME = "pref_name";
+ private static final String KEY_FORMAT = "pref_format";
+ private static final String KEY_DURATION = "pref_duration";
+ private static final String KEY_SIZE = "pref_size";
+ private static final String KEY_LOCATION = "pref_location";
+
+ private ColorMap colorMap;
+
+
+ public static Intent getStartIntent(Context context, String name, String format, long duration, long size, String location) {
+ Intent intent = new Intent(context, ActivityInformation.class);
+ intent.putExtra(KEY_NAME, name);
+ intent.putExtra(KEY_FORMAT, format);
+ intent.putExtra(KEY_DURATION, duration);
+ intent.putExtra(KEY_SIZE, size);
+ intent.putExtra(KEY_LOCATION, location);
+ return intent;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ colorMap = ARApplication.getInjector().provideColorMap();
+ setTheme(colorMap.getAppThemeResource());
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_info);
+
+ getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ LinearLayout toolbar = findViewById(R.id.toolbar);
+ toolbar.setPadding(0, AndroidUtils.getStatusBarHeight(getApplicationContext()), 0, 0);
+
+ Bundle extras = getIntent().getExtras();
+ TextView txtName = findViewById(R.id.txt_name);
+ TextView txtFormat = findViewById(R.id.txt_format);
+ TextView txtDuration = findViewById(R.id.txt_duration);
+ TextView txtSize = findViewById(R.id.txt_size);
+ TextView txtLocation = findViewById(R.id.txt_location);
+
+ if (extras != null) {
+ if (extras.containsKey(KEY_NAME)) {
+ txtName.setText(extras.getString(KEY_NAME));
+ }
+ if (extras.containsKey(KEY_FORMAT)) {
+ txtFormat.setText(extras.getString(KEY_FORMAT));
+ }
+ if (extras.containsKey(KEY_DURATION)) {
+ txtDuration.setText(TimeUtils.formatTimeIntervalHourMinSec2(extras.getLong(KEY_DURATION)));
+ }
+ if (extras.containsKey(KEY_SIZE)) {
+ txtSize.setText(AndroidUtils.formatSize(extras.getLong(KEY_SIZE)));
+ }
+ if (extras.containsKey(KEY_LOCATION)) {
+ txtLocation.setText(extras.getString(KEY_LOCATION));
+ }
+ }
+
+ findViewById(R.id.btn_back).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/main/MainActivity.java b/app/src/main/java/com/dimowner/audiorecorder/app/main/MainActivity.java
index 6b5088e7..cbdc37ee 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/main/MainActivity.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/main/MainActivity.java
@@ -26,14 +26,15 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
-import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
-import android.support.v4.content.FileProvider;
+import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.TypedValue;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -41,6 +42,7 @@
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -52,6 +54,7 @@
import com.dimowner.audiorecorder.R;
import com.dimowner.audiorecorder.app.PlaybackService;
import com.dimowner.audiorecorder.app.RecordingService;
+import com.dimowner.audiorecorder.app.info.ActivityInformation;
import com.dimowner.audiorecorder.app.records.RecordsActivity;
import com.dimowner.audiorecorder.app.settings.SettingsActivity;
import com.dimowner.audiorecorder.app.widget.WaveformView;
@@ -69,18 +72,18 @@ public class MainActivity extends Activity implements MainContract.View, View.On
// TODO: Fix WaveForm blinking when seek
// TODO: Show Record info
-// TODO: Ability to delete record by swipe left
// TODO: Ability to scroll up from the bottom of the list
// TODO: Ability to search by record name in list
-// TODO: Add pagination for records list
// TODO: Welcome screen
// TODO: Guidelines
// TODO: Check how work max recording duration
+// TODO: Add scroll animation to start when stop playback
public static final int REQ_CODE_REC_AUDIO_AND_WRITE_EXTERNAL = 101;
public static final int REQ_CODE_RECORD_AUDIO = 303;
public static final int REQ_CODE_WRITE_EXTERNAL_STORAGE = 404;
- public static final int REQ_CODE_READ_EXTERNAL_STORAGE = 405;
+ public static final int REQ_CODE_READ_EXTERNAL_STORAGE_IMPORT = 405;
+ public static final int REQ_CODE_READ_EXTERNAL_STORAGE_PLAYBACK = 406;
public static final int REQ_CODE_IMPORT_AUDIO = 11;
private WaveformView waveformView;
@@ -155,6 +158,7 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
});
presenter = ARApplication.getInjector().provideMainPresenter();
+ presenter.executeFirstRun();
waveformView.setOnSeekListener(new WaveformView.OnSeekListener() {
@Override
@@ -163,8 +167,9 @@ public void onSeek(int px) {
}
@Override
public void onSeeking(int px, long mills) {
- if (waveformView.getWaveformLength() > 0) {
- playProgress.setProgress(1000 * (int) AndroidUtils.pxToDp(px) / waveformView.getWaveformLength());
+ int length = waveformView.getWaveformLength();
+ if (length > 0) {
+ playProgress.setProgress(1000 * (int) AndroidUtils.pxToDp(px) / length);
}
txtProgress.setText(TimeUtils.formatTimeIntervalHourMinSec2(mills));
}
@@ -207,12 +212,20 @@ public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_play:
//This method Starts or Pause playback.
- presenter.startPlayback();
+ if (FileUtil.isFileInExternalStorage(presenter.getActiveRecordPath())) {
+ if (checkStoragePermissionPlayback()) {
+ presenter.startPlayback();
+ }
+ } else {
+ presenter.startPlayback();
+ }
break;
case R.id.btn_record:
- if (checkRecordPermission()) {
- //Start or stop recording
- presenter.startRecording();
+ if (checkRecordPermission2()) {
+ if (checkStoragePermission2()) {
+ //Start or stop recording
+ presenter.startRecording();
+ }
}
break;
case R.id.btn_stop:
@@ -225,25 +238,11 @@ public void onClick(View view) {
startActivity(SettingsActivity.getStartIntent(getApplicationContext()));
break;
case R.id.btn_share:
- String sharePath = presenter.getActiveRecordPath();
- if (sharePath != null) {
- Uri photoURI = FileProvider.getUriForFile(
- getApplicationContext(),
- getApplicationContext().getApplicationContext().getPackageName() + ".app_file_provider",
- new File(sharePath)
- );
- Intent share = new Intent(Intent.ACTION_SEND);
- share.setType("audio/*");
- share.putExtra(Intent.EXTRA_STREAM, photoURI);
- share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startActivity(Intent.createChooser(share, getResources().getString(R.string.share_record, presenter.getActiveRecordName())));
- } else {
- Timber.e("There no active record selected!");
- Toast.makeText(getApplicationContext(), R.string.please_select_record_to_share, Toast.LENGTH_LONG).show();
- }
+// AndroidUtils.shareAudioFile(getApplicationContext(), presenter.getActiveRecordPath(), presenter.getActiveRecordName());
+ showMenu(view);
break;
case R.id.btn_import:
- if (checkStoragePermission()) {
+ if (checkStoragePermissionImport()) {
startFileSelector();
}
break;
@@ -258,7 +257,9 @@ public void onClick(View view) {
private void startFileSelector() {
Intent intent_upload = new Intent();
intent_upload.setType("audio/*");
- intent_upload.setAction(Intent.ACTION_GET_CONTENT);
+ intent_upload.addCategory(Intent.CATEGORY_OPENABLE);
+// intent_upload.setAction(Intent.ACTION_GET_CONTENT);
+ intent_upload.setAction(Intent.ACTION_OPEN_DOCUMENT);
startActivityForResult(intent_upload, REQ_CODE_IMPORT_AUDIO);
}
@@ -301,6 +302,11 @@ public void showError(int resId) {
Toast.makeText(getApplicationContext(), resId, Toast.LENGTH_LONG).show();
}
+ @Override
+ public void showMessage(int resId) {
+ Toast.makeText(getApplicationContext(), resId, Toast.LENGTH_LONG).show();
+ }
+
@Override
public void showRecordingStart() {
btnRecord.setImageResource(R.drawable.ic_record_rec);
@@ -308,6 +314,7 @@ public void showRecordingStart() {
btnImport.setEnabled(false);
btnShare.setEnabled(false);
playProgress.setProgress(0);
+ playProgress.setEnabled(false);
txtDuration.setText(R.string.zero_time);
waveformView.showRecording();
}
@@ -318,6 +325,7 @@ public void showRecordingStop() {
btnPlay.setEnabled(true);
btnImport.setEnabled(true);
btnShare.setEnabled(true);
+ playProgress.setEnabled(true);
waveformView.hideRecording();
waveformView.clearRecordingData();
}
@@ -447,6 +455,11 @@ public void showName(String name) {
txtName.setText(name);
}
+ @Override
+ public void showRecordInfo(String name, String format, long duration, long size, String location) {
+ startActivity(ActivityInformation.getStartIntent(getApplicationContext(), name, format, duration, size, location));
+ }
+
@Override
public void updateRecordingView(List data) {
waveformView.setRecordingData(data);
@@ -481,6 +494,58 @@ public void hideRecordProcessing() {
pnlRecordProcessing.setVisibility(View.INVISIBLE);
}
+ private void showMenu(View v) {
+ PopupMenu popup = new PopupMenu(v.getContext(), v);
+ popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_share:
+ AndroidUtils.shareAudioFile(getApplicationContext(), presenter.getActiveRecordPath(), presenter.getActiveRecordName());
+ break;
+ case R.id.menu_info:
+ presenter.onRecordInfo();
+ break;
+ case R.id.menu_rename:
+ setRecordName(presenter.getActiveRecordId(), new File(presenter.getActiveRecordPath()));
+ break;
+ case R.id.menu_open_with:
+ AndroidUtils.openAudioFile(getApplicationContext(), presenter.getActiveRecordPath(), presenter.getActiveRecordName());
+ break;
+// case R.id.menu_download:
+// presenter.copyToDownloads(item.getPath(), item.getName());
+// break;
+ case R.id.menu_delete:
+ AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
+ builder.setTitle(R.string.warning)
+ .setIcon(R.drawable.ic_delete_forever)
+ .setMessage(R.string.delete_record)
+ .setCancelable(false)
+ .setPositiveButton(R.string.btn_yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ presenter.deleteActiveRecord();
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton(R.string.btn_no,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ break;
+ }
+ return false;
+ }
+ });
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.menu_more, popup.getMenu());
+ AndroidUtils.insertMenuItemIcons(v.getContext(), popup);
+ popup.show();
+ }
+
public void setRecordName(final long recordId, File file) {
//Create dialog layout programmatically.
LinearLayout container = new LinearLayout(getApplicationContext());
@@ -525,18 +590,22 @@ public void onClick(DialogInterface dialog, int id) {
if (!fileName.equalsIgnoreCase(newName)) {
presenter.renameRecord(recordId, newName);
}
- hideKeyboard();
dialog.dismiss();
}
})
.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
- hideKeyboard();
dialog.dismiss();
}
})
.create();
alertDialog.show();
+ alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ hideKeyboard();
+ }
+ });
editText.requestFocus();
editText.setSelection(editText.getText().length());
showKeyboard();
@@ -554,12 +623,71 @@ public void hideKeyboard(){
inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}
- private boolean checkStoragePermission() {
+ private boolean checkStoragePermissionImport() {
if (presenter.isStorePublic()) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&& checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, REQ_CODE_READ_EXTERNAL_STORAGE);
+ requestPermissions(
+ new String[]{
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQ_CODE_READ_EXTERNAL_STORAGE_IMPORT);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean checkStoragePermissionPlayback() {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
+ && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ new String[]{
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQ_CODE_READ_EXTERNAL_STORAGE_PLAYBACK);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean checkRecordPermission2() {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, REQ_CODE_RECORD_AUDIO);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean checkStoragePermission2() {
+ if (presenter.isStorePublic()) {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ AndroidUtils.showDialog(this, R.string.warning, R.string.need_write_permission,
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ requestPermissions(
+ new String[]{
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQ_CODE_WRITE_EXTERNAL_STORAGE);
+ }
+ },
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ presenter.setStoragePrivate(getApplicationContext());
+ presenter.startRecording();
+ }
+ }
+ );
return false;
}
}
@@ -597,7 +725,7 @@ && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageMan
}
@Override
- public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQ_CODE_REC_AUDIO_AND_WRITE_EXTERNAL && grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED
@@ -605,15 +733,28 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], in
presenter.startRecording();
} else if (requestCode == REQ_CODE_RECORD_AUDIO && grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- presenter.startRecording();
+ if (checkStoragePermission2()) {
+ presenter.startRecording();
+ }
} else if (requestCode == REQ_CODE_WRITE_EXTERNAL_STORAGE && grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
- presenter.startRecording();
- } else if (requestCode == REQ_CODE_READ_EXTERNAL_STORAGE && grantResults.length > 0
+ if (checkRecordPermission2()) {
+ presenter.startRecording();
+ }
+ } else if (requestCode == REQ_CODE_READ_EXTERNAL_STORAGE_IMPORT && grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
startFileSelector();
+ } else if (requestCode == REQ_CODE_READ_EXTERNAL_STORAGE_PLAYBACK && grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED
+ && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
+ presenter.startPlayback();
+ } else if (requestCode == REQ_CODE_WRITE_EXTERNAL_STORAGE
+ && (grantResults[0] == PackageManager.PERMISSION_DENIED
+ || grantResults[1] == PackageManager.PERMISSION_DENIED)) {
+ presenter.setStoragePrivate(getApplicationContext());
+ presenter.startRecording();
}
}
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/main/MainContract.java b/app/src/main/java/com/dimowner/audiorecorder/app/main/MainContract.java
index cc36dc66..51a0d0c6 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/main/MainContract.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/main/MainContract.java
@@ -57,11 +57,15 @@ interface View extends Contract.View {
void showDuration(String duration);
void showName(String name);
+ void showRecordInfo(String name, String format, long duration, long size, String location);
+
void updateRecordingView(List data);
}
interface UserActionsListener extends Contract.UserActionsListener {
+ void executeFirstRun();
+
void setAudioRecorder(RecorderContract.Recorder recorder);
void startRecording();
@@ -80,6 +84,8 @@ interface UserActionsListener extends Contract.UserActionsListener 0) {
+ view.onPlayProgress(mills, AndroidUtils.convertMillsToPx(mills,
+ AndroidUtils.dpToPx(dpPerSecond)), (int) (1000 * mills / duration));
+ }
}
}});
}
@@ -266,6 +272,13 @@ public void clear() {
recordingsTasks.close();
}
+ @Override
+ public void executeFirstRun() {
+ if (prefs.isFirstRun()) {
+ prefs.firstRunExecuted();
+ }
+ }
+
@Override
public void setAudioRecorder(RecorderContract.Recorder recorder) {
appRecorder.setRecorder(recorder);
@@ -454,8 +467,9 @@ public void run() {
}
});
}
- } catch (IOException e) {
+ } catch (IOException | OutOfMemoryError e) {
Timber.e(e);
+ view.showError(R.string.error_process_waveform);
}
isProcessing = false;
}
@@ -479,7 +493,13 @@ public void run() {
@Override
public void updateRecordingDir(Context context) {
- this.fileRepository.updateRecordingDir(context, prefs);
+ fileRepository.updateRecordingDir(context, prefs);
+ }
+
+ @Override
+ public void setStoragePrivate(Context context) {
+ prefs.setStoreDirPublic(false);
+ fileRepository.updateRecordingDir(context, prefs);
}
@Override
@@ -514,6 +534,49 @@ public int getActiveRecordId() {
}
}
+ @Override
+ public void deleteActiveRecord() {
+ if (record != null) {
+ audioPlayer.stop();
+ }
+ recordingsTasks.postRunnable(new Runnable() {
+ @Override public void run() {
+ localRepository.deleteRecord(record.getId());
+ fileRepository.deleteRecordFile(record.getPath());
+ if (record != null) {
+ prefs.setActiveRecord(-1);
+ dpPerSecond = AppConstants.SHORT_RECORD_DP_PER_SECOND;
+ }
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (view != null) {
+ view.showWaveForm(new int[]{}, 0);
+ view.showName("");
+ view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(0));
+ view.showMessage(R.string.record_deleted_successfully);
+ view.hideProgress();
+ record = null;
+ }
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ public void onRecordInfo() {
+ String format;
+ if (record.getPath().contains(AppConstants.M4A_EXTENSION)) {
+ format = AppConstants.M4A_EXTENSION;
+ } else if (record.getPath().contains(AppConstants.WAV_EXTENSION)) {
+ format = AppConstants.WAV_EXTENSION;
+ } else {
+ format = "";
+ }
+ view.showRecordInfo(record.getName(), format, record.getDuration()/1000, new File(record.getPath()).length(), record.getPath());
+ }
+
@Override
public void importAudioFile(final Context context, final Uri uri) {
if (view != null) {
@@ -533,18 +596,85 @@ public void run() {
File newFile = fileRepository.provideRecordFile(name);
if (FileUtil.copyFile(fileDescriptor, newFile)) {
- id = localRepository.insertFile(newFile.getAbsolutePath());
- prefs.setActiveRecord(id);
- }
- AndroidUtils.runOnUIThread(new Runnable() {
- @Override public void run() {
- if (view != null) {
- view.hideImportProgress();
- audioPlayer.stop();
- loadActiveRecord();
+ long duration = AndroidUtils.readRecordDuration(newFile);
+ if (duration/1000000 < AppConstants.LONG_RECORD_THRESHOLD_SECONDS) {
+ //Do simple import for short records.
+ id = localRepository.insertFile(newFile.getAbsolutePath());
+ prefs.setActiveRecord(id);
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override public void run() {
+ if (view != null) {
+ view.hideImportProgress();
+ audioPlayer.stop();
+ loadActiveRecord();
+ }
+ }
+ });
+ } else {
+ //Do 2 step import: 1) Import record with empty waveform. 2) Process and update waveform in background.
+ record = localRepository.insertRecord(
+ new Record(
+ Record.NO_ID,
+ newFile.getName(),
+ duration, //mills
+ newFile.lastModified(),
+ new Date().getTime(),
+ newFile.getAbsolutePath(),
+ false,
+ true,
+ new int[ARApplication.getLongWaveformSampleCount()]));
+
+ id = record.getId();
+ prefs.setActiveRecord(id);
+ songDuration = duration;
+ dpPerSecond = ARApplication.getDpPerSecond((float) songDuration / 1000000f);
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (view != null) {
+ audioPlayer.stop();
+ view.showWaveForm(record.getAmps(), songDuration);
+ view.showName(FileUtil.removeFileExtension(record.getName()));
+ view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(songDuration / 1000));
+ view.hideProgress();
+ }
+ }
+ });
+
+ try {
+ if (view != null) {
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (view != null) {
+ view.hideImportProgress();
+ view.showRecordProcessing();
+ }
+ }
+ });
+ isProcessing = true;
+ localRepository.updateWaveform((int)id);
+ record = localRepository.getRecord((int)id);
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (view != null) {
+ view.showWaveForm(record.getAmps(), songDuration);
+ view.hideRecordProcessing();
+ }
+ }
+ });
+ }
+ } catch (IOException | OutOfMemoryError e) {
+ Timber.e(e);
+ if (view != null) {
+ view.hideRecordProcessing();
+ view.showError(R.string.error_process_waveform);
+ }
}
+ isProcessing = false;
}
- });
+ }
} catch (SecurityException e) {
Timber.e(e);
AndroidUtils.runOnUIThread(new Runnable() {
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/records/EndlessRecyclerViewScrollListener.java b/app/src/main/java/com/dimowner/audiorecorder/app/records/EndlessRecyclerViewScrollListener.java
new file mode 100644
index 00000000..824410df
--- /dev/null
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/records/EndlessRecyclerViewScrollListener.java
@@ -0,0 +1,99 @@
+package com.dimowner.audiorecorder.app.records;
+
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.StaggeredGridLayoutManager;
+
+public abstract class EndlessRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
+ // Sets the starting page index
+ private static final int STARTING_PAGE_INDEX = 1;
+ // The minimum amount of items to have below your current scroll position
+ // before loading more.
+ private int visibleThreshold = 5;
+ // The current offset index of data you have loaded
+ private int currentPage = 1;
+ // The total number of items in the dataset after the last load
+ private int previousTotalItemCount = 0;
+ // True if we are still waiting for the last set of data to load.
+ private boolean loading = true;
+
+ private RecyclerView.LayoutManager mLayoutManager;
+
+ public EndlessRecyclerViewScrollListener(L layoutManager) {
+ this.mLayoutManager = layoutManager;
+ if (layoutManager instanceof StaggeredGridLayoutManager) {
+ visibleThreshold = visibleThreshold * ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
+ } else if (layoutManager instanceof GridLayoutManager) {
+ visibleThreshold = visibleThreshold * ((GridLayoutManager) layoutManager).getSpanCount();
+ }
+ }
+
+ private int getLastVisibleItem(int[] lastVisibleItemPositions) {
+ int maxSize = 0;
+ for (int i = 0; i < lastVisibleItemPositions.length; i++) {
+ if (i == 0) {
+ maxSize = lastVisibleItemPositions[i];
+ } else if (lastVisibleItemPositions[i] > maxSize) {
+ maxSize = lastVisibleItemPositions[i];
+ }
+ }
+ return maxSize;
+ }
+
+ // This happens many TIMES a second during a scroll, so be wary of the code you place here.
+ // We are given a few useful parameters to help us work out if we need to load some more data,
+ // but first we check if we are waiting for the previous load to finish.
+ @Override
+ public void onScrolled(RecyclerView view, int dx, int dy) {
+ int lastVisibleItemPosition = 0;
+ int totalItemCount = mLayoutManager.getItemCount();
+
+ if (mLayoutManager instanceof StaggeredGridLayoutManager) {
+ int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
+ // get maximum element within the list
+ lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
+ } else if (mLayoutManager instanceof LinearLayoutManager) {
+ lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
+ } else if (mLayoutManager instanceof GridLayoutManager) {
+ lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
+ }
+
+ // If the total item count is zero and the previous isn't, assume the
+ // list is invalidated and should be reset back to initial state
+ if (totalItemCount < previousTotalItemCount) {
+ this.currentPage = STARTING_PAGE_INDEX;
+ this.previousTotalItemCount = totalItemCount;
+ if (totalItemCount == 0) {
+ this.loading = true;
+ }
+ }
+ // If it’s still loading, we check to see if the dataset count has
+ // changed, if so we conclude it has finished loading and update the current page
+ // number and total item count.
+ if (loading && (totalItemCount > previousTotalItemCount+1)) {
+ loading = false;
+ previousTotalItemCount = totalItemCount;
+ }
+
+ // If it isn’t currently loading, we check to see if we have breached
+ // the visibleThreshold and need to reload more data.
+ // If we do need to reload some more data, we execute onLoadMore to fetch the data.
+ // threshold should reflect how many total columns there are too
+ if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount && totalItemCount > visibleThreshold) {
+ currentPage++;
+ onLoadMore(currentPage, totalItemCount);
+ loading = true;
+ }
+ }
+
+ // Defines the process for actually loading more data based on page
+ public abstract void onLoadMore(int page, int totalItemsCount);
+
+ //Used to reset inner state, if adapter data was fully changed
+ public void reset() {
+ currentPage = 1;
+ previousTotalItemCount = 0;
+ loading = true;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsActivity.java b/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsActivity.java
index e7c8616b..8aee74a2 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsActivity.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsActivity.java
@@ -16,6 +16,7 @@
package com.dimowner.audiorecorder.app.records;
+import android.Manifest;
import android.animation.Animator;
import android.app.Activity;
import android.app.AlertDialog;
@@ -24,14 +25,18 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.TypedValue;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
@@ -39,6 +44,7 @@
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -49,6 +55,7 @@
import com.dimowner.audiorecorder.ColorMap;
import com.dimowner.audiorecorder.R;
import com.dimowner.audiorecorder.app.PlaybackService;
+import com.dimowner.audiorecorder.app.info.ActivityInformation;
import com.dimowner.audiorecorder.app.widget.SimpleWaveformView;
import com.dimowner.audiorecorder.app.widget.TouchLayout;
import com.dimowner.audiorecorder.app.widget.WaveformView;
@@ -65,6 +72,8 @@
public class RecordsActivity extends Activity implements RecordsContract.View, View.OnClickListener {
+ public static final int REQ_CODE_READ_EXTERNAL_STORAGE_PLAYBACK = 406;
+
private RecyclerView recyclerView;
private LinearLayoutManager layoutManager;
private RecordsAdapter adapter;
@@ -78,12 +87,14 @@ public class RecordsActivity extends Activity implements RecordsContract.View, V
private ImageButton btnPrev;
private ImageButton btnDelete;
private ImageButton btnBookmarks;
+ private ImageButton btnSort;
private ImageButton btnCheckBookmark;
private TextView txtProgress;
private TextView txtDuration;
private TextView txtName;
private TextView txtEmpty;
private TextView txtTitle;
+ private TextView txtSubTitle;
private TouchLayout touchLayout;
private WaveformView waveformView;
private ProgressBar panelProgress;
@@ -108,7 +119,8 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_records);
- AndroidUtils.setTranslucent(this, true);
+ toolbar = findViewById(R.id.toolbar);
+// AndroidUtils.setTranslucent(this, true);
ImageButton btnBack = findViewById(R.id.btn_back);
btnBack.setOnClickListener(new View.OnClickListener() {
@@ -116,7 +128,6 @@ protected void onCreate(Bundle savedInstanceState) {
finish();
ARApplication.getInjector().releaseRecordsPresenter();
}});
- toolbar = findViewById(R.id.toolbar);
bottomDivider = findViewById(R.id.bottomDivider);
progressBar = findViewById(R.id.progress);
@@ -127,9 +138,11 @@ protected void onCreate(Bundle savedInstanceState) {
btnPrev = findViewById(R.id.btn_prev);
btnDelete = findViewById(R.id.btn_delete);
btnBookmarks = findViewById(R.id.btn_bookmarks);
+ btnSort = findViewById(R.id.btn_sort);
btnCheckBookmark = findViewById(R.id.btn_check_bookmark);
txtEmpty = findViewById(R.id.txtEmpty);
txtTitle = findViewById(R.id.txt_title);
+ txtSubTitle = findViewById(R.id.txt_sub_title);
btnPlay.setOnClickListener(this);
btnStop.setOnClickListener(this);
btnNext.setOnClickListener(this);
@@ -137,6 +150,7 @@ protected void onCreate(Bundle savedInstanceState) {
btnDelete.setOnClickListener(this);
btnBookmarks.setOnClickListener(this);
btnCheckBookmark.setOnClickListener(this);
+ btnSort.setOnClickListener(this);
playProgress = findViewById(R.id.play_progress);
txtProgress = findViewById(R.id.txt_progress);
@@ -178,10 +192,11 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
+ recyclerView.addOnScrollListener(new MyScrollListener(layoutManager));
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
- public void onScrolled(RecyclerView rv, int dx, int dy) {
+ public void onScrolled(@NonNull RecyclerView rv, int dx, int dy) {
super.onScrolled(rv, dx, dy);
handleToolbarScroll(dy);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@@ -212,8 +227,9 @@ public void onItemClick(View view, long id, String path, final int position) {
presenter.setActiveRecord(id, new RecordsContract.Callback() {
@Override public void onSuccess() {
presenter.stopPlayback();
- presenter.startPlayback();
- adapter.setActiveItem(position);
+ if (startPlayback()) {
+ adapter.setActiveItem(position);
+ }
}
@Override public void onError(Exception e) {
Timber.e(e);
@@ -229,12 +245,55 @@ public void onItemClick(View view, long id, String path, final int position) {
presenter.removeFromBookmarks(id);
}
});
+ adapter.setOnItemOptionListener(new RecordsAdapter.OnItemOptionListener() {
+ @Override
+ public void onItemOptionSelected(int menuId, final ListItem item) {
+ switch (menuId) {
+ case R.id.menu_share:
+ AndroidUtils.shareAudioFile(getApplicationContext(), item.getPath(), item.getName());
+ break;
+ case R.id.menu_info:
+ presenter.onRecordInfo(item.getName(), item.getDuration(), item.getPath());
+ break;
+ case R.id.menu_rename:
+ setRecordName(item.getId(), new File(item.getPath()));
+ break;
+ case R.id.menu_open_with:
+ AndroidUtils.openAudioFile(getApplicationContext(), item.getPath(), item.getName());
+ break;
+// case R.id.menu_download:
+// presenter.copyToDownloads(item.getPath(), item.getName());
+// break;
+ case R.id.menu_delete:
+ AlertDialog.Builder builder = new AlertDialog.Builder(RecordsActivity.this);
+ builder.setTitle(R.string.warning)
+ .setIcon(R.drawable.ic_delete_forever)
+ .setMessage(R.string.delete_record)
+ .setCancelable(false)
+ .setPositiveButton(R.string.btn_yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ presenter.deleteRecord(item.getId(), item.getPath());
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton(R.string.btn_no,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ break;
+ }
+ }
+ });
recyclerView.setAdapter(adapter);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- // Set the padding to match the Status Bar height
- toolbar.setPadding(0, AndroidUtils.getStatusBarHeight(getApplicationContext()), 0, 0);
- }
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+// // Set the padding to match the Status Bar height
+// toolbar.setPadding(0, AndroidUtils.getStatusBarHeight(getApplicationContext()), 0, 0);
+// }
presenter = ARApplication.getInjector().provideRecordsPresenter();
waveformView.setOnSeekListener(new WaveformView.OnSeekListener() {
@@ -336,12 +395,25 @@ private void showToolbar() {
AnimationUtil.viewAnimationY(toolbar, 0f, null);
}
+ private boolean startPlayback() {
+ if (FileUtil.isFileInExternalStorage(presenter.getActiveRecordPath())) {
+ if (checkStoragePermissionPlayback()) {
+ presenter.startPlayback();
+ return true;
+ }
+ } else {
+ presenter.startPlayback();
+ return true;
+ }
+ return false;
+ }
+
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_play:
//This method Starts or Pause playback.
- presenter.startPlayback();
+ startPlayback();
break;
case R.id.btn_stop:
presenter.stopPlayback();
@@ -353,18 +425,19 @@ public void onClick(View view) {
presenter.setActiveRecord(id, new RecordsContract.Callback() {
@Override public void onSuccess() {
presenter.stopPlayback();
- presenter.startPlayback();
- int pos = adapter.findPositionById(id);
- if (pos >= 0) {
- recyclerView.scrollToPosition(pos);
- int o = recyclerView.computeVerticalScrollOffset();
- if (o > 0) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- toolbar.setTranslationZ(getResources().getDimension(R.dimen.toolbar_elevation));
- toolbar.setBackgroundResource(colorMap.getPrimaryColorRes());
+ if (startPlayback()) {
+ int pos = adapter.findPositionById(id);
+ if (pos >= 0) {
+ recyclerView.scrollToPosition(pos);
+ int o = recyclerView.computeVerticalScrollOffset();
+ if (o > 0) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ toolbar.setTranslationZ(getResources().getDimension(R.dimen.toolbar_elevation));
+ toolbar.setBackgroundResource(colorMap.getPrimaryColorRes());
+ }
}
+ adapter.setActiveItem(pos);
}
- adapter.setActiveItem(pos);
}
}
@Override public void onError(Exception e) {
@@ -378,11 +451,12 @@ public void onClick(View view) {
presenter.setActiveRecord(id2, new RecordsContract.Callback() {
@Override public void onSuccess() {
presenter.stopPlayback();
- presenter.startPlayback();
- int pos2 = adapter.findPositionById(id2);
- if (pos2 >= 0) {
- recyclerView.scrollToPosition(pos2);
- adapter.setActiveItem(pos2);
+ if (startPlayback()) {
+ int pos2 = adapter.findPositionById(id2);
+ if (pos2 >= 0) {
+ recyclerView.scrollToPosition(pos2);
+ adapter.setActiveItem(pos2);
+ }
}
}
@Override public void onError(Exception e) {
@@ -418,6 +492,9 @@ public void onClick(DialogInterface dialog, int id) {
case R.id.btn_bookmarks:
presenter.applyBookmarksFilter();
break;
+ case R.id.btn_sort:
+ showMenu(view);
+ break;
case R.id.txt_name:
if (presenter.getActiveRecordId() != -1) {
setRecordName(presenter.getActiveRecordId(), new File(presenter.getActiveRecordPath()));
@@ -426,6 +503,31 @@ public void onClick(DialogInterface dialog, int id) {
}
}
+ private void showMenu(View v) {
+ PopupMenu popup = new PopupMenu(v.getContext(), v);
+ popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_date:
+ presenter.updateRecordsOrder(AppConstants.SORT_DATE);
+ break;
+ case R.id.menu_name:
+ presenter.updateRecordsOrder(AppConstants.SORT_NAME);
+ break;
+ case R.id.menu_duration:
+ presenter.updateRecordsOrder(AppConstants.SORT_DURATION);
+ break;
+ }
+ return false;
+ }
+ });
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.menu_sort, popup.getMenu());
+ AndroidUtils.insertMenuItemIcons(v.getContext(), popup);
+ popup.show();
+ }
+
@Override
protected void onStart() {
super.onStart();
@@ -451,11 +553,11 @@ public void onBackPressed() {
private void handleToolbarScroll(int dy) {
float inset = toolbar.getTranslationY() - dy;
int height;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- height = toolbar.getHeight() + AndroidUtils.getStatusBarHeight(getApplicationContext());
- } else {
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+// height = toolbar.getHeight() + AndroidUtils.getStatusBarHeight(getApplicationContext());
+// } else {
height = toolbar.getHeight();
- }
+// }
if (inset < -height) {
inset = -height;
@@ -533,12 +635,12 @@ public void run() {
}
@Override
- public void showRecords(List records) {
+ public void showRecords(List records, int order) {
if (records.size() == 0) {
- txtEmpty.setVisibility(View.VISIBLE);
- adapter.setData(new ArrayList());
+// txtEmpty.setVisibility(View.VISIBLE);
+ adapter.setData(new ArrayList(), order);
} else {
- adapter.setData(records);
+ adapter.setData(records, order);
txtEmpty.setVisibility(View.GONE);
if (touchLayout.getVisibility() == View.VISIBLE) {
adapter.showFooter();
@@ -546,6 +648,12 @@ public void showRecords(List records) {
}
}
+ @Override
+ public void addRecords(List records, int order) {
+ adapter.addData(records, order);
+ txtEmpty.setVisibility(View.GONE);
+ }
+
@Override
public void showEmptyList() {
txtEmpty.setText(R.string.no_records);
@@ -575,10 +683,15 @@ public void showRecordName(String name) {
@Override
public void onDeleteRecord(long id) {
- adapter.deleteItem(id);
+// adapter.deleteItem(id);
+ presenter.loadRecords();
if (adapter.getAudioRecordsCount() == 0) {
showEmptyList();
}
+ }
+
+ @Override
+ public void hidePlayPanel() {
hidePanel();
}
@@ -598,16 +711,40 @@ public void removedFromBookmarks(int id, boolean isActive) {
adapter.markRemovedFromBookmarks(id);
}
+ @Override
+ public void showSortType(int type) {
+ switch (type) {
+ case AppConstants.SORT_DATE:
+ txtSubTitle.setText(R.string.by_date);
+ break;
+ case AppConstants.SORT_NAME:
+ txtSubTitle.setText(R.string.by_name);
+ break;
+ case AppConstants.SORT_DURATION:
+ txtSubTitle.setText(R.string.by_duration);
+ break;
+ }
+ }
+
@Override
public void bookmarksSelected() {
btnBookmarks.setImageResource(R.drawable.ic_bookmark);
txtTitle.setText(R.string.bookmarks);
+ btnSort.setVisibility(View.GONE);
+ txtSubTitle.setVisibility(View.GONE);
}
@Override
public void bookmarksUnselected() {
btnBookmarks.setImageResource(R.drawable.ic_bookmark_bordered);
txtTitle.setText(R.string.records);
+ btnSort.setVisibility(View.VISIBLE);
+ txtSubTitle.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void showRecordInfo(String name, String format, long duration, long size, String location) {
+ startActivity(ActivityInformation.getStartIntent(getApplicationContext(), name, format, duration, size, location));
}
@Override
@@ -630,6 +767,11 @@ public void showError(int resId) {
Toast.makeText(getApplicationContext(), resId, Toast.LENGTH_LONG).show();
}
+ @Override
+ public void showMessage(int resId) {
+ Toast.makeText(getApplicationContext(), resId, Toast.LENGTH_LONG).show();
+ }
+
public void setRecordName(final long recordId, File file) {
//Create dialog layout programmatically.
LinearLayout container = new LinearLayout(getApplicationContext());
@@ -673,19 +815,24 @@ public void onClick(DialogInterface dialog, int id) {
String newName = editText.getText().toString();
if (!fileName.equalsIgnoreCase(newName)) {
presenter.renameRecord(recordId, newName);
+ presenter.loadRecords();
}
- hideKeyboard();
dialog.dismiss();
}
})
.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
- hideKeyboard();
dialog.dismiss();
}
})
.create();
alertDialog.show();
+ alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ hideKeyboard();
+ }
+ });
editText.requestFocus();
editText.setSelection(editText.getText().length());
showKeyboard();
@@ -702,4 +849,41 @@ public void hideKeyboard(){
InputMethodManager inputMethodManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}
+
+ private boolean checkStoragePermissionPlayback() {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
+ && checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(
+ new String[]{
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQ_CODE_READ_EXTERNAL_STORAGE_PLAYBACK);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requestCode == REQ_CODE_READ_EXTERNAL_STORAGE_PLAYBACK && grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED
+ && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
+ presenter.startPlayback();
+ }
+ }
+
+ public class MyScrollListener extends EndlessRecyclerViewScrollListener {
+
+ public MyScrollListener(L layoutManager) {
+ super(layoutManager);
+ }
+
+ @Override
+ public void onLoadMore(int page, int totalItemsCount) {
+// Timber.v("onLoadMore page = " + page + " count = " + totalItemsCount);
+ presenter.loadRecordsPage(page);
+ }
+ }
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsAdapter.java b/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsAdapter.java
index d48cae82..bdcf20a4 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsAdapter.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsAdapter.java
@@ -17,19 +17,22 @@
package com.dimowner.audiorecorder.app.records;
import android.graphics.Typeface;
-import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.PopupMenu;
import android.widget.TextView;
+import com.dimowner.audiorecorder.AppConstants;
import com.dimowner.audiorecorder.R;
import com.dimowner.audiorecorder.app.widget.SimpleWaveformView;
import com.dimowner.audiorecorder.util.AndroidUtils;
@@ -48,8 +51,9 @@ public class RecordsAdapter extends RecyclerView.Adapter();
}
@@ -60,11 +64,11 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
View view = new View(viewGroup.getContext());
int height;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- height = AndroidUtils.getStatusBarHeight(viewGroup.getContext()) + (int) viewGroup.getContext().getResources().getDimension(R.dimen.toolbar_height);
- } else {
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+// height = AndroidUtils.getStatusBarHeight(viewGroup.getContext()) + (int) viewGroup.getContext().getResources().getDimension(R.dimen.toolbar_height);
+// } else {
height = (int) viewGroup.getContext().getResources().getDimension(R.dimen.toolbar_height);
- }
+// }
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, height);
@@ -101,16 +105,17 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
}
@Override
- public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, final int p) {
+ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, final int pos) {
if (viewHolder.getItemViewType() == ListItem.ITEM_TYPE_NORMAL) {
final ItemViewHolder holder = (ItemViewHolder) viewHolder;
+ final int p = holder.getAdapterPosition();
holder.name.setText(data.get(p).getName());
holder.description.setText(data.get(p).getDurationStr());
holder.created.setText(data.get(p).getAddedTimeStr());
if (data.get(p).isBookmarked()) {
- holder.bookmark.setImageResource(R.drawable.ic_bookmark_small);
+ holder.btnBookmark.setImageResource(R.drawable.ic_bookmark_small);
} else {
- holder.bookmark.setImageResource(R.drawable.ic_bookmark_bordered_small);
+ holder.btnBookmark.setImageResource(R.drawable.ic_bookmark_bordered_small);
}
if (viewHolder.getLayoutPosition() == activeItem) {
holder.view.setBackgroundResource(R.color.selected_item_color);
@@ -118,7 +123,7 @@ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder,
holder.view.setBackgroundResource(android.R.color.transparent);
}
- holder.bookmark.setOnClickListener(new View.OnClickListener() {
+ holder.btnBookmark.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (onAddToBookmarkListener != null && data.size() > p) {
@@ -130,6 +135,12 @@ public void onClick(View v) {
}
}
});
+ holder.btnMore.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showMenu(v, p);
+ }
+ });
holder.waveformView.setWaveform(data.get(p).getAmps());
holder.view.setOnClickListener(new View.OnClickListener() {
@@ -141,25 +152,42 @@ public void onClick(View v) {
}});
} else if (viewHolder.getItemViewType() == ListItem.ITEM_TYPE_DATE) {
UniversalViewHolder holder = (UniversalViewHolder) viewHolder;
- ((TextView)holder.view).setText(TimeUtils.formatDateSmart(data.get(p).getAdded(), holder.view.getContext()));
+ ((TextView)holder.view).setText(TimeUtils.formatDateSmart(data.get(viewHolder.getAdapterPosition()).getAdded(), holder.view.getContext()));
}
}
- public void setActiveItem(int activeItem) {
+ private void showMenu(View v, final int pos) {
+ PopupMenu popup = new PopupMenu(v.getContext(), v);
+ popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (onItemOptionListener != null) {
+ onItemOptionListener.onItemOptionSelected(item.getItemId(), data.get(pos));
+ }
+ return false;
+ }
+ });
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.menu_more, popup.getMenu());
+ AndroidUtils.insertMenuItemIcons(v.getContext(), popup);
+ popup.show();
+ }
+
+ void setActiveItem(int activeItem) {
int prev = this.activeItem;
this.activeItem = activeItem;
notifyItemChanged(prev);
notifyItemChanged(activeItem);
}
- public void setActiveItemById(long id) {
- int pos = findPositionById(id);
- if (pos >= 0) {
- setActiveItem(pos);
- }
- }
+// public void setActiveItemById(long id) {
+// int pos = findPositionById(id);
+// if (pos >= 0) {
+// setActiveItem(pos);
+// }
+// }
- public int findPositionById(long id) {
+ int findPositionById(long id) {
if (id >= 0) {
for (int i = 0; i < data.size() - 1; i++) {
if (data.get(i).getId() == id) {
@@ -180,46 +208,88 @@ public int getItemViewType(int position) {
return data.get(position).getType();
}
- public void setData(List data) {
- this.data = data;
+ void setData(List d, int order) {
+ updateShowHeader(order);
if (showDateHeaders) {
- addDateHeaders();
+ data = addDateHeaders(d);
+ } else {
+ data = d;
}
- this.data.add(0, ListItem.createHeaderItem());
+ data.add(0, ListItem.createHeaderItem());
notifyDataSetChanged();
}
- private void addDateHeaders() {
+// public void addData(List d) {
+// this.data.addAll(addDateHeaders(d));
+// notifyItemRangeInserted(data.size() - d.size(), d.size());
+// }
+
+ void addData(List d, int order) {
+ if (data.size() > 0) {
+ updateShowHeader(order);
+ if (showDateHeaders) {
+ if (findFooter() >= 0) {
+ data.addAll(data.size() - 1, addDateHeaders(d));
+ } else {
+ data.addAll(addDateHeaders(d));
+ }
+ } else {
+ if (findFooter() >= 0) {
+ data.addAll(data.size() - 1, d);
+ } else {
+ data.addAll(d);
+ }
+ }
+ notifyItemRangeInserted(data.size() - d.size(), d.size());
+ }
+ }
+
+ private void updateShowHeader(int order) {
+ if (order == AppConstants.SORT_DATE) {
+ showDateHeaders = true;
+ } else {
+ showDateHeaders = false;
+ }
+ }
+
+ public ListItem getItem(int pos) {
+ return data.get(pos);
+ }
+
+ private List addDateHeaders(List data) {
if (data.size() > 0) {
- data.add(0, ListItem.createDateItem(data.get(0).getAdded()));
+ if (!hasDateHeader(data, data.get(0).getAdded())) {
+ data.add(0, ListItem.createDateItem(data.get(0).getAdded()));
+ }
Calendar d1 = Calendar.getInstance();
d1.setTimeInMillis(data.get(0).getAdded());
Calendar d2 = Calendar.getInstance();
for (int i = 1; i < data.size(); i++) {
d1.setTimeInMillis(data.get(i - 1).getAdded());
d2.setTimeInMillis(data.get(i).getAdded());
- if (!TimeUtils.isSameDay(d1, d2)) {
+ if (!TimeUtils.isSameDay(d1, d2) && !hasDateHeader(data, data.get(i).getAdded())) {
data.add(i, ListItem.createDateItem(data.get(i).getAdded()));
}
}
}
+ return data;
}
- public void deleteItem(long id) {
- for (int i = 0; i < data.size(); i++) {
- if (data.get(i).getId() == id) {
- data.remove(i);
- if (getAudioRecordsCount() == 0) {
- data.clear();
- notifyDataSetChanged();
- } else {
- notifyItemRemoved(i);
- }
- }
- }
- }
-
- public int getAudioRecordsCount() {
+// public void deleteItem(long id) {
+// for (int i = 0; i < data.size(); i++) {
+// if (data.get(i).getId() == id) {
+// data.remove(i);
+// if (getAudioRecordsCount() == 0) {
+// data.clear();
+// notifyDataSetChanged();
+// } else {
+// notifyItemRemoved(i);
+// }
+// }
+// }
+// }
+
+ int getAudioRecordsCount() {
int count = 0;
for (int i = 0; i < data.size(); i++) {
if (data.get(i).getType() == ListItem.ITEM_TYPE_NORMAL) {
@@ -244,7 +314,7 @@ public void hideFooter() {
}
}
- public long getNextTo(long id) {
+ long getNextTo(long id) {
if (id >= 0) {
for (int i = 0; i < data.size() - 1; i++) {
if (data.get(i).getId() == id) {
@@ -259,7 +329,7 @@ public long getNextTo(long id) {
return -1;
}
- public long getPrevTo(long id) {
+ long getPrevTo(long id) {
if (id >= 0) {
for (int i = 1; i < data.size(); i++) {
if (data.get(i).getId() == id) {
@@ -283,7 +353,7 @@ private int findFooter() {
return -1;
}
- public void markAddedToBookmarks(int id) {
+ void markAddedToBookmarks(int id) {
for (int i = 0; i < data.size(); i++) {
if (data.get(i).getId() == id) {
data.get(i).setBookmarked(true);
@@ -292,7 +362,7 @@ public void markAddedToBookmarks(int id) {
}
}
- public void markRemovedFromBookmarks(int id) {
+ void markRemovedFromBookmarks(int id) {
for (int i = 0; i < data.size(); i++) {
if (data.get(i).getId() == id) {
data.get(i).setBookmarked(false);
@@ -301,11 +371,26 @@ public void markRemovedFromBookmarks(int id) {
}
}
- public void setItemClickListener(ItemClickListener itemClickListener) {
+ private boolean hasDateHeader(List data, long time) {
+ for (int i = data.size()-1; i>= 0; i--) {
+ if (data.get(i).getType() == ListItem.ITEM_TYPE_DATE) {
+ Calendar d1 = Calendar.getInstance();
+ d1.setTimeInMillis(data.get(i).getAdded());
+ Calendar d2 = Calendar.getInstance();
+ d2.setTimeInMillis(time);
+ if (TimeUtils.isSameDay(d1, d2)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void setItemClickListener(ItemClickListener itemClickListener) {
this.itemClickListener = itemClickListener;
}
- public void setOnAddToBookmarkListener(OnAddToBookmarkListener onAddToBookmarkListener) {
+ void setOnAddToBookmarkListener(OnAddToBookmarkListener onAddToBookmarkListener) {
this.onAddToBookmarkListener = onAddToBookmarkListener;
}
@@ -313,37 +398,44 @@ public interface ItemClickListener{
void onItemClick(View view, long id, String path, int position);
}
+ void setOnItemOptionListener(OnItemOptionListener onItemOptionListener) {
+ this.onItemOptionListener = onItemOptionListener;
+ }
public interface OnAddToBookmarkListener {
void onAddToBookmarks(int id);
void onRemoveFromBookmarks(int id);
}
+ public interface OnItemOptionListener {
+ void onItemOptionSelected(int menuId, ListItem item);
+ }
+
public class ItemViewHolder extends RecyclerView.ViewHolder {
TextView name;
TextView description;
TextView created;
- // ImageView image;
- ImageButton bookmark;
+ ImageButton btnBookmark;
+ ImageButton btnMore;
SimpleWaveformView waveformView;
View view;
- public ItemViewHolder(View itemView) {
+ ItemViewHolder(View itemView) {
super(itemView);
- this.view = itemView;
- this.name = itemView.findViewById(R.id.list_item_name);
- this.description = itemView.findViewById(R.id.list_item_description);
- this.created = itemView.findViewById(R.id.list_item_date);
-// this.image = itemView.findViewById(R.id.list_item_image);
- this.bookmark = itemView.findViewById(R.id.bookmark);
- this.waveformView = itemView.findViewById(R.id.list_item_waveform);
+ view = itemView;
+ name = itemView.findViewById(R.id.list_item_name);
+ description = itemView.findViewById(R.id.list_item_description);
+ created = itemView.findViewById(R.id.list_item_date);
+ btnBookmark = itemView.findViewById(R.id.list_item_bookmark);
+ waveformView = itemView.findViewById(R.id.list_item_waveform);
+ btnMore = itemView.findViewById(R.id.list_item_more);
}
}
public class UniversalViewHolder extends RecyclerView.ViewHolder {
View view;
- public UniversalViewHolder(View view) {
+ UniversalViewHolder(View view) {
super(view);
this.view = view;
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsContract.java b/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsContract.java
index 1d0a714a..f52397ee 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsContract.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/records/RecordsContract.java
@@ -40,7 +40,9 @@ interface View extends Contract.View {
void showWaveForm(int[] waveForm, long duration);
void showDuration(String duration);
- void showRecords(List records);
+ void showRecords(List records, int order);
+ void addRecords(List records, int order);
+
void showEmptyList();
void showEmptyBookmarksList();
@@ -51,11 +53,17 @@ interface View extends Contract.View {
void onDeleteRecord(long id);
+ void hidePlayPanel();
+
void addedToBookmarks(int id, boolean isActive);
void removedFromBookmarks(int id, boolean isActive);
+ void showSortType(int type);
+
void bookmarksSelected();
void bookmarksUnselected();
+
+ void showRecordInfo(String name, String format, long duration, long size, String location);
}
interface UserActionsListener extends Contract.UserActionsListener {
@@ -74,10 +82,18 @@ interface UserActionsListener extends Contract.UserActionsListener 0) {
+ view.onPlayProgress(mills, AndroidUtils.convertMillsToPx(mills,
+ AndroidUtils.dpToPx(dpPerSecond)), (int) (1000 * mills / duration));
+ }
}
}});
}
@@ -161,6 +168,9 @@ public void onError(AppException throwable) {
view.showPlayStart();
}
}
+ if (view != null) {
+ view.showSortType(prefs.getRecordsOrder());
+ }
}
@Override
@@ -182,9 +192,9 @@ public void clear() {
@Override
public void startPlayback() {
if (!appRecorder.isRecording()) {
- if (record != null) {
+ if (activeRecord != null) {
if (!audioPlayer.isPlaying()) {
- audioPlayer.setData(record.getPath());
+ audioPlayer.setData(activeRecord.getPath());
}
audioPlayer.playOrPause();
}
@@ -218,25 +228,37 @@ public void playPrev() {
@Override
public void deleteActiveRecord() {
- audioPlayer.stop();
+ if (activeRecord != null) {
+ deleteRecord(activeRecord.getId(), activeRecord.getPath());
+ }
+ }
+
+ @Override
+ public void deleteRecord(final long id, final String path) {
+ if (activeRecord != null && activeRecord.getId() == id) {
+ audioPlayer.stop();
+ }
recordingsTasks.postRunnable(new Runnable() {
@Override public void run() {
- if (record != null) {
- localRepository.deleteRecord(record.getId());
- fileRepository.deleteRecordFile(record.getPath());
+ localRepository.deleteRecord((int)id);
+ fileRepository.deleteRecordFile(path);
+ if (activeRecord != null && activeRecord.getId() == id) {
prefs.setActiveRecord(-1);
- final long id = record.getId();
- record = null;
dpPerSecond = AppConstants.SHORT_RECORD_DP_PER_SECOND;
- AndroidUtils.runOnUIThread(new Runnable() {
- @Override
- public void run() {
- if (view != null) {
- view.onDeleteRecord(id);
+ }
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (view != null) {
+ view.onDeleteRecord(id);
+ if (activeRecord != null && activeRecord.getId() == id) {
+ view.hidePlayPanel();
+ view.showMessage(R.string.record_deleted_successfully);
+ activeRecord = null;
}
}
- });
- }
+ }
+ });
}
});
}
@@ -283,10 +305,10 @@ public void renameRecord(final long id, String n) {
ext = AppConstants.M4A_EXTENSION;
}
if (fileRepository.renameFile(r.getPath(), name, ext)) {
- record = new Record(r.getId(), nameWithExt, r.getDuration(), r.getCreated(),
+ activeRecord = new Record(r.getId(), nameWithExt, r.getDuration(), r.getCreated(),
r.getAdded(), renamed.getAbsolutePath(), r.isBookmarked(),
r.isWaveformProcessed(), r.getAmps());
- if (localRepository.updateRecord(record)) {
+ if (localRepository.updateRecord(activeRecord)) {
AndroidUtils.runOnUIThread(new Runnable() {
@Override public void run() {
if (view != null) {
@@ -330,6 +352,26 @@ record = new Record(r.getId(), nameWithExt, r.getDuration(), r.getCreated(),
}});
}
+ @Override
+ public void copyToDownloads(final String path, final String name) {
+ if (view != null) {
+ //TODO: show copy progress
+ copyTasks.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ FileUtil.copyFile(new File(path), FileUtil.createFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), name));
+ //TODO: show success result
+ } catch (IOException e) {
+ Timber.v(e);
+ //TODO: show copy error
+ }
+ //TODO:hide progress
+ }
+ });
+ }
+ }
+
@Override
public void loadRecords() {
if (view != null) {
@@ -338,21 +380,22 @@ public void loadRecords() {
loadingTasks.postRunnable(new Runnable() {
@Override
public void run() {
- final List recordList = localRepository.getAllRecords();
- record = localRepository.getRecord((int) prefs.getActiveRecord());
- if (record != null) {
- dpPerSecond = ARApplication.getDpPerSecond((float) record.getDuration() / 1000000f);
+ final int order = prefs.getRecordsOrder();
+ final List recordList = localRepository.getRecords(0, order);
+ activeRecord = localRepository.getRecord((int) prefs.getActiveRecord());
+ if (activeRecord != null) {
+ dpPerSecond = ARApplication.getDpPerSecond((float) activeRecord.getDuration() / 1000000f);
}
AndroidUtils.runOnUIThread(new Runnable() {
@Override
public void run() {
if (view != null) {
- view.showRecords(Mapper.recordsToListItems(recordList));
- if (record != null) {
- view.showWaveForm(record.getAmps(), record.getDuration());
- view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(record.getDuration() / 1000));
- view.showRecordName(FileUtil.removeFileExtension(record.getName()));
- if (record.isBookmarked()) {
+ view.showRecords(Mapper.recordsToListItems(recordList), order);
+ if (activeRecord != null) {
+ view.showWaveForm(activeRecord.getAmps(), activeRecord.getDuration());
+ view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(activeRecord.getDuration() / 1000));
+ view.showRecordName(FileUtil.removeFileExtension(activeRecord.getName()));
+ if (activeRecord.isBookmarked()) {
view.bookmarksSelected();
} else {
view.bookmarksUnselected();
@@ -372,6 +415,53 @@ public void run() {
}
}
+ @Override
+ public void updateRecordsOrder(int order) {
+ prefs.setRecordOrder(order);
+ view.showSortType(order);
+ loadRecords();
+ }
+
+ @Override
+ public void loadRecordsPage(final int page) {
+ if (view != null) {
+ view.showProgress();
+ view.showPanelProgress();
+ loadingTasks.postRunnable(new Runnable() {
+ @Override
+ public void run() {
+ final int order = prefs.getRecordsOrder();
+ final List recordList = localRepository.getRecords(page, order);
+ activeRecord = localRepository.getRecord((int) prefs.getActiveRecord());
+ if (activeRecord != null) {
+ dpPerSecond = ARApplication.getDpPerSecond((float) activeRecord.getDuration() / 1000000f);
+ }
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (view != null) {
+ view.addRecords(Mapper.recordsToListItems(recordList), order);
+ if (activeRecord != null) {
+ view.showWaveForm(activeRecord.getAmps(), activeRecord.getDuration());
+ view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(activeRecord.getDuration() / 1000));
+ view.showRecordName(FileUtil.removeFileExtension(activeRecord.getName()));
+ if (activeRecord.isBookmarked()) {
+ view.bookmarksSelected();
+ } else {
+ view.bookmarksUnselected();
+ }
+ }
+ view.hideProgress();
+ view.hidePanelProgress();
+ view.bookmarksUnselected();
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+
public void loadBookmarks() {
if (!showBookmarks) {
loadRecords();
@@ -383,19 +473,19 @@ public void loadBookmarks() {
@Override
public void run() {
final List recordList = localRepository.getBookmarks();
- record = localRepository.getRecord((int) prefs.getActiveRecord());
- if (record != null) {
- dpPerSecond = ARApplication.getDpPerSecond((float) record.getDuration() / 1000000f);
+ activeRecord = localRepository.getRecord((int) prefs.getActiveRecord());
+ if (activeRecord != null) {
+ dpPerSecond = ARApplication.getDpPerSecond((float) activeRecord.getDuration() / 1000000f);
}
AndroidUtils.runOnUIThread(new Runnable() {
@Override
public void run() {
if (view != null) {
- view.showRecords(Mapper.recordsToListItems(recordList));
- if (record != null) {
- view.showWaveForm(record.getAmps(), record.getDuration());
- view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(record.getDuration() / 1000));
- view.showRecordName(FileUtil.removeFileExtension(record.getName()));
+ view.showRecords(Mapper.recordsToListItems(recordList), AppConstants.SORT_DATE);
+ if (activeRecord != null) {
+ view.showWaveForm(activeRecord.getAmps(), activeRecord.getDuration());
+ view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(activeRecord.getDuration() / 1000));
+ view.showRecordName(FileUtil.removeFileExtension(activeRecord.getName()));
}
view.hideProgress();
view.hidePanelProgress();
@@ -423,25 +513,27 @@ public void checkBookmarkActiveRecord() {
recordingsTasks.postRunnable(new Runnable() {
@Override
public void run() {
- if (record.isBookmarked()) {
- localRepository.removeFromBookmarks(record.getId());
- } else {
- localRepository.addToBookmarks(record.getId());
- }
- record.setBookmark(!record.isBookmarked());
+ if (activeRecord != null) {
+ if (activeRecord.isBookmarked()) {
+ localRepository.removeFromBookmarks(activeRecord.getId());
+ } else {
+ localRepository.addToBookmarks(activeRecord.getId());
+ }
+ activeRecord.setBookmark(!activeRecord.isBookmarked());
- AndroidUtils.runOnUIThread(new Runnable() {
- @Override
- public void run() {
- if (view != null) {
- if (record.isBookmarked()) {
- view.addedToBookmarks(record.getId(), true);
- } else {
- view.removedFromBookmarks(record.getId(), true);
+ AndroidUtils.runOnUIThread(new Runnable() {
+ @Override
+ public void run() {
+ if (view != null && activeRecord != null) {
+ if (activeRecord.isBookmarked()) {
+ view.addedToBookmarks(activeRecord.getId(), true);
+ } else {
+ view.removedFromBookmarks(activeRecord.getId(), true);
+ }
}
}
- }
- });
+ });
+ }
}
});
}
@@ -458,7 +550,7 @@ public void run() {
@Override
public void run() {
if (view != null) {
- view.addedToBookmarks(r.getId(), r.getId() == record.getId());
+ view.addedToBookmarks(r.getId(), activeRecord != null && r.getId() == activeRecord.getId());
}
}
});
@@ -479,7 +571,7 @@ public void run() {
@Override
public void run() {
if (view != null) {
- view.removedFromBookmarks(r.getId(), r.getId() == record.getId());
+ view.removedFromBookmarks(r.getId(), activeRecord != null && r.getId() == activeRecord.getId());
}
}
});
@@ -498,21 +590,21 @@ public void setActiveRecord(final long id, final RecordsContract.Callback callba
loadingTasks.postRunnable(new Runnable() {
@Override
public void run() {
- record = localRepository.getRecord((int) id);
- if (record != null) {
- dpPerSecond = ARApplication.getDpPerSecond((float) record.getDuration()/1000000f);
+ activeRecord = localRepository.getRecord((int) id);
+ if (activeRecord != null) {
+ dpPerSecond = ARApplication.getDpPerSecond((float) activeRecord.getDuration()/1000000f);
AndroidUtils.runOnUIThread(new Runnable() {
@Override
public void run() {
- if (view != null) {
- view.showWaveForm(record.getAmps(), record.getDuration());
- view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(record.getDuration() / 1000));
- view.showRecordName(FileUtil.removeFileExtension(record.getName()));
+ if (view != null && activeRecord != null) {
+ view.showWaveForm(activeRecord.getAmps(), activeRecord.getDuration());
+ view.showDuration(TimeUtils.formatTimeIntervalHourMinSec2(activeRecord.getDuration() / 1000));
+ view.showRecordName(FileUtil.removeFileExtension(activeRecord.getName()));
callback.onSuccess();
- if (record.isBookmarked()) {
- view.addedToBookmarks(record.getId(), true);
+ if (activeRecord.isBookmarked()) {
+ view.addedToBookmarks(activeRecord.getId(), true);
} else {
- view.removedFromBookmarks(record.getId(), true);
+ view.removedFromBookmarks(activeRecord.getId(), true);
}
view.hidePanelProgress();
view.showPlayerPanel();
@@ -542,8 +634,8 @@ public long getActiveRecordId() {
@Override
public String getActiveRecordPath() {
- if (record != null) {
- return record.getPath();
+ if (activeRecord != null) {
+ return activeRecord.getPath();
} else {
return null;
}
@@ -551,10 +643,23 @@ public String getActiveRecordPath() {
@Override
public String getRecordName() {
- if (record != null) {
- return record.getName();
+ if (activeRecord != null) {
+ return activeRecord.getName();
} else {
return "Record";
}
}
+
+ @Override
+ public void onRecordInfo(String name, long duration, String location) {
+ String format;
+ if (location.contains(AppConstants.M4A_EXTENSION)) {
+ format = AppConstants.M4A_EXTENSION;
+ } else if (location.contains(AppConstants.WAV_EXTENSION)) {
+ format = AppConstants.WAV_EXTENSION;
+ } else {
+ format = "";
+ }
+ view.showRecordInfo(name, format, duration, new File(location).length(), location);
+ }
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java
index d3c6369b..895ef774 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java
@@ -30,9 +30,12 @@
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.CompoundButton;
import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
@@ -42,6 +45,7 @@
import com.dimowner.audiorecorder.AppConstants;
import com.dimowner.audiorecorder.ColorMap;
import com.dimowner.audiorecorder.R;
+import com.dimowner.audiorecorder.util.AndroidUtils;
import java.util.ArrayList;
import java.util.List;
@@ -57,6 +61,7 @@ public class SettingsActivity extends Activity implements SettingsContract.View,
private Switch swPublicDir;
private Switch swRecordInStereo;
private Switch swKeepScreenOn;
+ private Switch swAskToRename;
private Spinner formatSelector;
private Spinner sampleRateSelector;
@@ -80,19 +85,31 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
+ getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ LinearLayout toolbar = findViewById(R.id.toolbar);
+ toolbar.setPadding(0, AndroidUtils.getStatusBarHeight(getApplicationContext()), 0, 0);
+
+ View space = findViewById(R.id.space);
+ ViewGroup.LayoutParams params = space.getLayoutParams();
+ params.height = AndroidUtils.getNavigationBarHeight(getApplicationContext());
+ space.setLayoutParams(params);
+
ImageButton btnBack = findViewById(R.id.btn_back);
- TextView btnDeleteAll = findViewById(R.id.btnDeleteAll);
+// TextView btnDeleteAll = findViewById(R.id.btnDeleteAll);
TextView btnRate = findViewById(R.id.btnRate);
TextView btnRequest = findViewById(R.id.btnRequest);
TextView txtAbout = findViewById(R.id.txtAbout);
txtAbout.setText(getAboutContent());
btnBack.setOnClickListener(this);
- btnDeleteAll.setOnClickListener(this);
+// btnDeleteAll.setOnClickListener(this);
btnRate.setOnClickListener(this);
btnRequest.setOnClickListener(this);
swPublicDir = findViewById(R.id.swPublicDir);
swRecordInStereo = findViewById(R.id.swRecordInStereo);
swKeepScreenOn = findViewById(R.id.swKeepScreenOn);
+ swAskToRename = findViewById(R.id.swAskToRename);
txtRecordsCount = findViewById(R.id.txt_records_count);
txtTotalDuration= findViewById(R.id.txt_total_duration);
@@ -117,6 +134,12 @@ public void onCheckedChanged(CompoundButton btn, boolean isChecked) {
presenter.keepScreenOn(isChecked);
}
});
+ swAskToRename.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton btn, boolean isChecked) {
+ presenter.askToRenameAfterRecordingStop(isChecked);
+ }
+ });
presenter = ARApplication.getInjector().provideSettingsPresenter();
@@ -262,27 +285,27 @@ public void onClick(View v) {
case R.id.btnRate:
rateApp();
break;
- case R.id.btnDeleteAll:
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.warning)
- .setIcon(R.drawable.ic_delete_forever)
- .setMessage(R.string.delete_all_records)
- .setCancelable(false)
- .setPositiveButton(R.string.btn_yes, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- presenter.deleteAllRecords();
- dialog.dismiss();
- }
- })
- .setNegativeButton(R.string.btn_no,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- dialog.dismiss();
- }
- });
- AlertDialog alert = builder.create();
- alert.show();
- break;
+// case R.id.btnDeleteAll:
+// AlertDialog.Builder builder = new AlertDialog.Builder(this);
+// builder.setTitle(R.string.warning)
+// .setIcon(R.drawable.ic_delete_forever)
+// .setMessage(R.string.delete_all_records)
+// .setCancelable(false)
+// .setPositiveButton(R.string.btn_yes, new DialogInterface.OnClickListener() {
+// public void onClick(DialogInterface dialog, int id) {
+// presenter.deleteAllRecords();
+// dialog.dismiss();
+// }
+// })
+// .setNegativeButton(R.string.btn_no,
+// new DialogInterface.OnClickListener() {
+// public void onClick(DialogInterface dialog, int id) {
+// dialog.dismiss();
+// }
+// });
+// AlertDialog alert = builder.create();
+// alert.show();
+// break;
case R.id.btnRequest:
requestFeature();
break;
@@ -313,7 +336,9 @@ private void requestFeature() {
"[" + getResources().getString(R.string.app_name) + "] - " + getResources().getString(R.string.request)
);
try {
- startActivity(Intent.createChooser(i, getResources().getString(R.string.send_email)));
+ Intent chooser = Intent.createChooser(i, getResources().getString(R.string.send_email));
+ chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(chooser);
} catch (android.content.ActivityNotFoundException ex) {
showError(R.string.email_clients_not_found);
}
@@ -363,6 +388,11 @@ public void showRecordInStereo(boolean b) {
swRecordInStereo.setChecked(b);
}
+ @Override
+ public void showAskToRenameAfterRecordingStop(boolean b) {
+ swAskToRename.setChecked(b);
+ }
+
@Override
public void showRecordingBitrate(int bitrate) {
bitrateSelector.setSelection(bitrate);
@@ -432,4 +462,9 @@ public void showError(String message) {
public void showError(int resId) {
Toast.makeText(getApplicationContext(), resId, Toast.LENGTH_LONG).show();
}
+
+ @Override
+ public void showMessage(int resId) {
+ Toast.makeText(getApplicationContext(), resId, Toast.LENGTH_LONG).show();
+ }
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java
index 4b039be2..c7d3868a 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java
@@ -11,6 +11,8 @@ interface View extends Contract.View {
void showKeepScreenOn(boolean b);
void showRecordInStereo(boolean b);
+ void showAskToRenameAfterRecordingStop(boolean b);
+
void showRecordingBitrate(int bitrate);
void showRecordingSampleRate(int rate);
@@ -37,6 +39,8 @@ public interface UserActionsListener extends Contract.UserActionsListener= 0; i--) {
+// path.lineTo(i * dpi, half + 1 + waveformData[i]);
+// }
+// path.lineTo(0, half);
+// path.close();
+// canvas.drawPath(path, waveformPaint);
+
float dpi = AndroidUtils.dpToPx(1);
- for (int i = 1; i < width; i++) {
- path.lineTo(i * dpi, half - waveformData[i]);
- }
- for (int i = width - 1; i >= 0; i--) {
- path.lineTo(i * dpi, half + 1 + waveformData[i]);
+ float[] lines = new float[width*4+4];
+ int step = 0;
+ for (int i = 0; i < width; i++) {
+ lines[step] = i*dpi;
+ lines[step+1] = half + waveformData[i];
+ lines[step+2] = i*dpi;
+ lines[step+3] = half - waveformData[i];
+ step +=4;
}
- path.lineTo(0, half);
- path.close();
- canvas.drawPath(path, waveformPaint);
+ //Horizontal zero line
+ lines[step] = 0;
+ lines[step+1] = half;
+ lines[step+2] = width*dpi;
+ lines[step+3] = half;
+ canvas.drawLines(lines, 0, lines.length, waveformPaint);
}
/**
diff --git a/app/src/main/java/com/dimowner/audiorecorder/audio/SoundFile.java b/app/src/main/java/com/dimowner/audiorecorder/audio/SoundFile.java
index d2c43a8c..32c24833 100755
--- a/app/src/main/java/com/dimowner/audiorecorder/audio/SoundFile.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/audio/SoundFile.java
@@ -31,6 +31,8 @@
import java.nio.ShortBuffer;
import java.util.Arrays;
+import timber.log.Timber;
+
/**
* This class taken from Ringdroid app.
* https://github.com/google/ringdroid
@@ -39,6 +41,8 @@ public class SoundFile {
private File mInputFile = null;
+ private boolean isFailed = false;
+
private float dpPerSec = AppConstants.SHORT_RECORD_DP_PER_SECOND;
private int mFileSize;
private int mSampleRate;
@@ -62,7 +66,7 @@ private SoundFile() {
}
// Create and return a SoundFile object using the file fileName.
- public static SoundFile create(String fileName) throws IOException, FileNotFoundException {
+ public static SoundFile create(String fileName) throws IOException, OutOfMemoryError, FileNotFoundException {
// First check that the file exists and that its extension is supported.
File f = new File(fileName);
if (!f.exists()) {
@@ -100,7 +104,7 @@ public int[] getFrameGains() {
return mFrameGains;
}
- private void readFile(File inputFile) throws IOException {
+ private void readFile(File inputFile) throws IOException, OutOfMemoryError {
MediaExtractor extractor = new MediaExtractor();
MediaFormat format = null;
int i;
@@ -124,11 +128,15 @@ private void readFile(File inputFile) throws IOException {
mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
// Expected total number of samples per channel.
- int expectedNumSamples =
- (int) ((format.getLong(MediaFormat.KEY_DURATION) / 1000000.f) * mSampleRate + 0.5f);
+ int expectedNumSamples = 0;
+ try {
+ expectedNumSamples = (int) ((format.getLong(MediaFormat.KEY_DURATION) / 1000000.f) * mSampleRate + 0.5f);
- //SoundFile duration.
- duration = format.getLong(MediaFormat.KEY_DURATION);
+ //SoundFile duration.
+ duration = format.getLong(MediaFormat.KEY_DURATION);
+ } catch (Exception e) {
+ Timber.e(e);
+ }
dpPerSec = ARApplication.getDpPerSecond((float) duration/1000000f);
MediaCodec codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
@@ -149,7 +157,13 @@ private void readFile(File inputFile) throws IOException {
// For longer streams, the buffer size will be increased later on, calculating a rough
// estimate of the total size needed to store all the samples in order to resize the buffer
// only once.
- mDecodedBytes = ByteBuffer.allocate(1 << 20);
+ try {
+ mDecodedBytes = ByteBuffer.allocate(1 << 20);
+ } catch (IllegalArgumentException e) {
+ Timber.e(e);
+ mDecodedBytes = ByteBuffer.allocate(1 << 10);
+ }
+
Boolean firstSampleData = true;
while (true) {
// read data from file and feed it to the decoder input buffers.
@@ -215,6 +229,8 @@ private void readFile(File inputFile) throws IOException {
if (retry == 0) {
// Failed to allocate memory... Stop reading more data and finalize the
// instance with the data decoded so far.
+ mFrameGains = new int[ARApplication.getLongWaveformSampleCount()];
+ isFailed = true;
break;
}
//ByteBuffer newDecodedBytes = ByteBuffer.allocate(newSize);
@@ -257,31 +273,33 @@ private void readFile(File inputFile) throws IOException {
codec.release();
codec = null;
- // Temporary hack to make it work with the old version.
- mNumFrames = mNumSamples / getSamplesPerFrame();
- if (mNumSamples % getSamplesPerFrame() != 0) {
- mNumFrames++;
- }
- mFrameGains = new int[mNumFrames];
- int j;
- int gain, value;
- for (i = 0; i < mNumFrames; i++) {
- gain = -1;
- for (j = 0; j < getSamplesPerFrame(); j++) {
- value = 0;
- for (int k = 0; k < mChannels; k++) {
- if (mDecodedSamples.remaining() > 0) {
- value += Math.abs(mDecodedSamples.get());
+ if (!isFailed) {
+ // Temporary hack to make it work with the old version.
+ mNumFrames = mNumSamples / getSamplesPerFrame();
+ if (mNumSamples % getSamplesPerFrame() != 0) {
+ mNumFrames++;
+ }
+ mFrameGains = new int[mNumFrames];
+ int j;
+ int gain, value;
+ for (i = 0; i < mNumFrames; i++) {
+ gain = -1;
+ for (j = 0; j < getSamplesPerFrame(); j++) {
+ value = 0;
+ for (int k = 0; k < mChannels; k++) {
+ if (mDecodedSamples.remaining() > 0) {
+ value += Math.abs(mDecodedSamples.get());
+ }
+ }
+ value /= mChannels;
+ if (gain < value) {
+ gain = value;
}
}
- value /= mChannels;
- if (gain < value) {
- gain = value;
- }
+ mFrameGains[i] = (int) Math.sqrt(gain); // here gain = sqrt(max value of 1st channel)...
}
- mFrameGains[i] = (int) Math.sqrt(gain); // here gain = sqrt(max value of 1st channel)...
+ mDecodedSamples.rewind();
+ // DumpSamples(); // Uncomment this line to dump the samples in a TSV file.
}
- mDecodedSamples.rewind();
- // DumpSamples(); // Uncomment this line to dump the samples in a TSV file.
}
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/audio/player/AudioPlayer.java b/app/src/main/java/com/dimowner/audiorecorder/audio/player/AudioPlayer.java
index f95fed5c..67a67c13 100755
--- a/app/src/main/java/com/dimowner/audiorecorder/audio/player/AudioPlayer.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/audio/player/AudioPlayer.java
@@ -21,6 +21,7 @@
import com.dimowner.audiorecorder.AppConstants;
import com.dimowner.audiorecorder.exception.AppException;
+import com.dimowner.audiorecorder.exception.PermissionDeniedException;
import com.dimowner.audiorecorder.exception.PlayerDataSourceException;
import java.io.IOException;
import java.util.ArrayList;
@@ -70,16 +71,8 @@ public void setData(String data) {
if (mediaPlayer != null && dataSource != null && dataSource.equals(data)) {
//Do nothing
} else {
- try {
- isPrepared = false;
- mediaPlayer = new MediaPlayer();
- mediaPlayer.setDataSource(data);
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- dataSource = data;
- } catch (IOException e) {
- Timber.e(e);
- onError(new PlayerDataSourceException());
- }
+ dataSource = data;
+ restartPlayer();
}
}
@@ -90,53 +83,70 @@ private void restartPlayer() {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(dataSource);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- } catch (IOException e) {
+ } catch (IOException | IllegalArgumentException | IllegalStateException | SecurityException e) {
Timber.e(e);
- onError(new PlayerDataSourceException());
+ if (e.getMessage().contains("Permission denied")) {
+ onError(new PermissionDeniedException());
+ } else {
+ onError(new PlayerDataSourceException());
+ }
}
}
}
@Override
public void playOrPause() {
- if (mediaPlayer != null) {
- if (mediaPlayer.isPlaying()) {
- pause();
- } else {
- if (!isPrepared) {
- try {
- mediaPlayer.setOnPreparedListener(this);
- mediaPlayer.prepareAsync();
- } catch (IllegalStateException ex) {
- Timber.e(ex);
- restartPlayer();
- mediaPlayer.setOnPreparedListener(this);
- mediaPlayer.prepareAsync();
- }
+ try {
+ if (mediaPlayer != null) {
+ if (mediaPlayer.isPlaying()) {
+ pause();
} else {
- mediaPlayer.start();
- mediaPlayer.seekTo((int) seekPos);
- onStartPlay();
- mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- stop();
- onStopPlay();
- }
- });
-
- timerProgress = new Timer();
- timerProgress.schedule(new TimerTask() {
- @Override
- public void run() {
- if (mediaPlayer != null && mediaPlayer.isPlaying()) {
- int curPos = mediaPlayer.getCurrentPosition();
- onPlayProgress(curPos);
+ if (!isPrepared) {
+ try {
+ mediaPlayer.setOnPreparedListener(this);
+ mediaPlayer.prepareAsync();
+ } catch (IllegalStateException ex) {
+ Timber.e(ex);
+ restartPlayer();
+ mediaPlayer.setOnPreparedListener(this);
+ try {
+ mediaPlayer.prepareAsync();
+ } catch (IllegalStateException e) {
+ Timber.e(e);
+ restartPlayer();
}
}
- }, 0, AppConstants.VISUALIZATION_INTERVAL);
+ } else {
+ mediaPlayer.start();
+ mediaPlayer.seekTo((int) seekPos);
+ onStartPlay();
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ stop();
+ onStopPlay();
+ }
+ });
+
+ timerProgress = new Timer();
+ timerProgress.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ if (mediaPlayer != null && mediaPlayer.isPlaying()) {
+ int curPos = mediaPlayer.getCurrentPosition();
+ onPlayProgress(curPos);
+ }
+ } catch(IllegalStateException e){
+ Timber.e(e, "Player is not initialized!");
+ }
+ }
+ }, 0, AppConstants.VISUALIZATION_INTERVAL);
+ }
}
}
+ } catch(IllegalStateException e){
+ Timber.e(e, "Player is not initialized!");
}
}
@@ -164,9 +174,13 @@ public void onCompletion(MediaPlayer mp) {
timerProgress.schedule(new TimerTask() {
@Override
public void run() {
- if (mediaPlayer != null && mediaPlayer.isPlaying()) {
- int curPos = mediaPlayer.getCurrentPosition();
- onPlayProgress(curPos);
+ try {
+ if (mediaPlayer != null && mediaPlayer.isPlaying()) {
+ int curPos = mediaPlayer.getCurrentPosition();
+ onPlayProgress(curPos);
+ }
+ } catch(IllegalStateException e){
+ Timber.e(e, "Player is not initialized!");
}
}
}, 0, AppConstants.VISUALIZATION_INTERVAL);
@@ -175,9 +189,13 @@ public void run() {
@Override
public void seek(long mills) {
seekPos = mills;
- if (mediaPlayer != null && mediaPlayer.isPlaying()) {
- mediaPlayer.seekTo((int) seekPos);
- onSeek((int) seekPos);
+ try {
+ if (mediaPlayer != null && mediaPlayer.isPlaying()) {
+ mediaPlayer.seekTo((int) seekPos);
+ onSeek((int) seekPos);
+ }
+ } catch(IllegalStateException e){
+ Timber.e(e, "Player is not initialized!");
}
}
@@ -214,7 +232,12 @@ public void stop() {
@Override
public boolean isPlaying() {
- return mediaPlayer != null && mediaPlayer.isPlaying();
+ try {
+ return mediaPlayer != null && mediaPlayer.isPlaying();
+ } catch(IllegalStateException e){
+ Timber.e(e, "Player is not initialized!");
+ }
+ return false;
}
@Override
diff --git a/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/AudioRecorder.java b/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/AudioRecorder.java
index 26239579..8161f0f0 100755
--- a/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/AudioRecorder.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/AudioRecorder.java
@@ -106,7 +106,7 @@ public void startRecording() {
if (recorderCallback != null) {
recorderCallback.onStartRecord();
}
- } catch (IllegalStateException e) {
+ } catch (RuntimeException e) {
Timber.e(e, "startRecording() failed");
if (recorderCallback != null) {
recorderCallback.onError(new RecorderInitException());
diff --git a/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/WavRecorder.java b/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/WavRecorder.java
index f3f03310..d9537fa5 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/WavRecorder.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/audio/recorder/WavRecorder.java
@@ -74,19 +74,30 @@ public void prepare(String outputFile, int channelCount, int sampleRate, int bit
recordFile = new File(outputFile);
if (recordFile.exists() && recordFile.isFile()) {
int channel = channelCount == 1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO;
- bufferSize = AudioRecord.getMinBufferSize(sampleRate,
- channel,
- AudioFormat.ENCODING_PCM_16BIT);
-
- recorder = new AudioRecord(
- MediaRecorder.AudioSource.MIC,
- sampleRate,
- channel,
- AudioFormat.ENCODING_PCM_16BIT,
- bufferSize
+ try {
+ bufferSize = AudioRecord.getMinBufferSize(sampleRate,
+ channel,
+ AudioFormat.ENCODING_PCM_16BIT);
+ Timber.v("buffer size = %s", bufferSize);
+ if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) {
+ bufferSize = AudioRecord.getMinBufferSize(sampleRate,
+ channel,
+ AudioFormat.ENCODING_PCM_16BIT);
+ }
+ recorder = new AudioRecord(
+ MediaRecorder.AudioSource.MIC,
+ sampleRate,
+ channel,
+ AudioFormat.ENCODING_PCM_16BIT,
+ bufferSize
);
-
- if (recorder.getState() == AudioRecord.STATE_INITIALIZED) {
+ } catch (IllegalArgumentException e) {
+ Timber.e(e, "sampleRate = " + sampleRate + " channel = " + channel + " bufferSize = " + bufferSize);
+ if (recorder != null) {
+ recorder.release();
+ }
+ }
+ if (recorder != null && recorder.getState() == AudioRecord.STATE_INITIALIZED) {
if (recorderCallback != null) {
recorderCallback.onPrepareRecord();
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/data/Prefs.java b/app/src/main/java/com/dimowner/audiorecorder/data/Prefs.java
index 62f256d2..b34f163d 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/data/Prefs.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/data/Prefs.java
@@ -24,6 +24,9 @@ public interface Prefs {
boolean isStoreDirPublic();
void setStoreDirPublic(boolean b);
+ boolean isAskToRenameAfterStopRecording();
+ void setAskToRenameAfterStopRecording(boolean b);
+
long getActiveRecord();
void setActiveRecord(long id);
@@ -47,4 +50,7 @@ public interface Prefs {
void setSampleRate(int rate);
int getSampleRate();
+
+ void setRecordOrder(int order);
+ int getRecordsOrder();
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/data/PrefsImpl.java b/app/src/main/java/com/dimowner/audiorecorder/data/PrefsImpl.java
index f87e6cdb..6f764a0b 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/data/PrefsImpl.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/data/PrefsImpl.java
@@ -30,6 +30,7 @@ public class PrefsImpl implements Prefs {
private static final String PREF_KEY_IS_FIRST_RUN = "is_first_run";
private static final String PREF_KEY_IS_STORE_DIR_PUBLIC = "is_store_dir_public";
+ private static final String PREF_KEY_IS_ASK_TO_RENAME_AFTER_STOP_RECORDING = "is_ask_rename_after_stop_recording";
private static final String PREF_KEY_ACTIVE_RECORD = "active_record";
private static final String PREF_KEY_RECORD_COUNTER = "record_counter";
private static final String PREF_KEY_THEME_COLORMAP_POSITION = "theme_color";
@@ -37,6 +38,7 @@ public class PrefsImpl implements Prefs {
private static final String PREF_KEY_FORMAT = "pref_format";
private static final String PREF_KEY_BITRATE = "pref_bitrate";
private static final String PREF_KEY_SAMPLE_RATE = "pref_sample_rate";
+ private static final String PREF_KEY_RECORDS_ORDER = "pref_records_order";
//Recording prefs.
private static final String PREF_KEY_RECORD_CHANNEL_COUNT = "record_channel_count";
@@ -69,12 +71,15 @@ public boolean isFirstRun() {
public void firstRunExecuted() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(PREF_KEY_IS_FIRST_RUN, false);
+ editor.putBoolean(PREF_KEY_IS_STORE_DIR_PUBLIC, true);
+ editor.putBoolean(PREF_KEY_IS_ASK_TO_RENAME_AFTER_STOP_RECORDING, true);
editor.apply();
+// setStoreDirPublic(true);
}
@Override
public boolean isStoreDirPublic() {
- return sharedPreferences.contains(PREF_KEY_IS_STORE_DIR_PUBLIC) && sharedPreferences.getBoolean(PREF_KEY_IS_STORE_DIR_PUBLIC, false);
+ return sharedPreferences.contains(PREF_KEY_IS_STORE_DIR_PUBLIC) && sharedPreferences.getBoolean(PREF_KEY_IS_STORE_DIR_PUBLIC, true);
}
@Override
@@ -84,6 +89,18 @@ public void setStoreDirPublic(boolean b) {
editor.apply();
}
+ @Override
+ public boolean isAskToRenameAfterStopRecording() {
+ return sharedPreferences.contains(PREF_KEY_IS_ASK_TO_RENAME_AFTER_STOP_RECORDING) && sharedPreferences.getBoolean(PREF_KEY_IS_ASK_TO_RENAME_AFTER_STOP_RECORDING, true);
+ }
+
+ @Override
+ public void setAskToRenameAfterStopRecording(boolean b) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(PREF_KEY_IS_ASK_TO_RENAME_AFTER_STOP_RECORDING, b);
+ editor.apply();
+ }
+
@Override
public long getActiveRecord() {
return sharedPreferences.getLong(PREF_KEY_ACTIVE_RECORD, -1);
@@ -179,4 +196,16 @@ public void setSampleRate(int rate) {
public int getSampleRate() {
return sharedPreferences.getInt(PREF_KEY_SAMPLE_RATE, AppConstants.RECORD_SAMPLE_RATE_44100);
}
+
+ @Override
+ public void setRecordOrder(int order) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putInt(PREF_KEY_RECORDS_ORDER, order);
+ editor.apply();
+ }
+
+ @Override
+ public int getRecordsOrder() {
+ return sharedPreferences.getInt(PREF_KEY_RECORDS_ORDER, AppConstants.SORT_DATE);
+ }
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/data/database/DataSource.java b/app/src/main/java/com/dimowner/audiorecorder/data/database/DataSource.java
index d973afe0..b9fbd188 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/data/database/DataSource.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/data/database/DataSource.java
@@ -26,6 +26,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
+import com.dimowner.audiorecorder.AppConstants;
import com.dimowner.audiorecorder.BuildConfig;
/**
@@ -135,6 +136,30 @@ public ArrayList getAll() {
return convertCursor(cursor);
}
+ /**
+ * Get records from database for table T.
+ * @return List that contains all records of table T.
+ */
+ public ArrayList getRecords(int page) {
+ Cursor cursor = queryLocal("SELECT * FROM " + tableName
+ + " ORDER BY " + SQLiteHelper.COLUMN_DATE_ADDED + " DESC"
+ + " LIMIT " + AppConstants.DEFAULT_PER_PAGE
+ + " OFFSET " + (page-1) * AppConstants.DEFAULT_PER_PAGE);
+ return convertCursor(cursor);
+ }
+
+ /**
+ * Get records from database for table T.
+ * @return List that contains all records of table T.
+ */
+ public ArrayList getRecords(int page, String order) {
+ Cursor cursor = queryLocal("SELECT * FROM " + tableName
+ + " ORDER BY " + order
+ + " LIMIT " + AppConstants.DEFAULT_PER_PAGE
+ + " OFFSET " + (page-1) * AppConstants.DEFAULT_PER_PAGE);
+ return convertCursor(cursor);
+ }
+
/**
* Delete all records from the table
* @throws SQLException on error
diff --git a/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepository.java b/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepository.java
index c0e54721..dc433bdc 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepository.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepository.java
@@ -29,6 +29,10 @@ public interface LocalRepository {
List getAllRecords();
+ List getRecords(int page);
+
+ List getRecords(int page, int order);
+
boolean deleteAllRecords();
Record getLastRecord();
@@ -41,7 +45,7 @@ public interface LocalRepository {
long insertFile(String filePath, long duration, int[] waveform) throws IOException;
- boolean updateWaveform(int id) throws IOException;
+ boolean updateWaveform(int id) throws IOException, OutOfMemoryError;
void deleteRecord(int id);
diff --git a/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepositoryImpl.java b/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepositoryImpl.java
index 7ae3a3f6..87f8bc67 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepositoryImpl.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/data/database/LocalRepositoryImpl.java
@@ -19,6 +19,7 @@
import android.database.Cursor;
import android.database.SQLException;
+import com.dimowner.audiorecorder.AppConstants;
import com.dimowner.audiorecorder.audio.SoundFile;
import java.io.File;
@@ -169,7 +170,7 @@ public long insertFile(String path, long duration, int[] waveform) throws IOExce
}
@Override
- public boolean updateWaveform(int id) throws IOException {
+ public boolean updateWaveform(int id) throws IOException, OutOfMemoryError {
Record record = getRecord(id);
String path = record.getPath();
if (path != null && !path.isEmpty()) {
@@ -215,6 +216,48 @@ public List getAllRecords() {
return list;
}
+ @Override
+ public List getRecords(int page) {
+ if (!dataSource.isOpen()) {
+ dataSource.open();
+ }
+ List list = dataSource.getRecords(page);
+ //Remove not records with not existing audio files (which was lost or deleted)
+ for (int i = 0; i < list.size(); i++) {
+ if (!isFileExists(list.get(i).getPath())) {
+ dataSource.deleteItem(list.get(i).getId());
+ }
+ }
+ return list;
+ }
+
+ @Override
+ public List getRecords(int page, int order) {
+ if (!dataSource.isOpen()) {
+ dataSource.open();
+ }
+ String orderStr;
+ switch (order) {
+ case AppConstants.SORT_NAME:
+ orderStr = SQLiteHelper.COLUMN_NAME + " ASC";
+ break;
+ case AppConstants.SORT_DURATION:
+ orderStr = SQLiteHelper.COLUMN_DURATION + " DESC";
+ break;
+ case AppConstants.SORT_DATE:
+ default:
+ orderStr = SQLiteHelper.COLUMN_DATE_ADDED + " DESC";
+ }
+ List list = dataSource.getRecords(page, orderStr);
+ //Remove not records with not existing audio files (which was lost or deleted)
+ for (int i = 0; i < list.size(); i++) {
+ if (!isFileExists(list.get(i).getPath())) {
+ dataSource.deleteItem(list.get(i).getId());
+ }
+ }
+ return list;
+ }
+
@Override
public Record getLastRecord() {
if (!dataSource.isOpen()) {
diff --git a/app/src/main/java/com/dimowner/audiorecorder/exception/AppException.java b/app/src/main/java/com/dimowner/audiorecorder/exception/AppException.java
index f6f42105..cf56ae36 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/exception/AppException.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/exception/AppException.java
@@ -22,7 +22,9 @@ public abstract class AppException extends Exception {
public static final int INVALID_OUTPUT_FILE = 2;
public static final int RECORDER_INIT_EXCEPTION = 3;
public static final int PLAYER_INIT_EXCEPTION = 4;
- public static final int PLAYER_DATA_SOURCE_EXCEPTION= 5;
+ public static final int PLAYER_DATA_SOURCE_EXCEPTION = 5;
+ public static final int CANT_PROCESS_RECORD = 6;
+ public static final int READ_PERMISSION_DENIED = 7;
public abstract int getType();
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/exception/CantProcessRecord.java b/app/src/main/java/com/dimowner/audiorecorder/exception/CantProcessRecord.java
new file mode 100644
index 00000000..977c8451
--- /dev/null
+++ b/app/src/main/java/com/dimowner/audiorecorder/exception/CantProcessRecord.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2018 Dmitriy Ponomarenko
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.dimowner.audiorecorder.exception;
+
+public class CantProcessRecord extends AppException {
+ @Override
+ public int getType() {
+ return AppException.CANT_PROCESS_RECORD;
+ }
+}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/exception/ErrorParser.java b/app/src/main/java/com/dimowner/audiorecorder/exception/ErrorParser.java
index 3efdc091..f032741d 100644
--- a/app/src/main/java/com/dimowner/audiorecorder/exception/ErrorParser.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/exception/ErrorParser.java
@@ -33,6 +33,10 @@ public static int parseException(AppException e) {
return R.string.error_player_data_source;
} else if (e.getType() == AppException.PLAYER_INIT_EXCEPTION) {
return R.string.error_failed_to_init_player;
+ } else if (e.getType() == AppException.CANT_PROCESS_RECORD) {
+ return R.string.error_process_waveform;
+ } else if (e.getType() == AppException.READ_PERMISSION_DENIED) {
+ return R.string.error_permission_denied;
}
return R.string.error_unknown;
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/exception/PermissionDeniedException.java b/app/src/main/java/com/dimowner/audiorecorder/exception/PermissionDeniedException.java
new file mode 100644
index 00000000..1796be70
--- /dev/null
+++ b/app/src/main/java/com/dimowner/audiorecorder/exception/PermissionDeniedException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2018 Dmitriy Ponomarenko
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.dimowner.audiorecorder.exception;
+
+public class PermissionDeniedException extends AppException {
+ @Override
+ public int getType() {
+ return AppException.READ_PERMISSION_DENIED;
+ }
+}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/util/AndroidUtils.java b/app/src/main/java/com/dimowner/audiorecorder/util/AndroidUtils.java
index 847dd773..18284152 100755
--- a/app/src/main/java/com/dimowner/audiorecorder/util/AndroidUtils.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/util/AndroidUtils.java
@@ -17,22 +17,42 @@
package com.dimowner.audiorecorder.util;
import android.app.Activity;
+import android.app.Dialog;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.media.MediaExtractor;
import android.media.MediaFormat;
+import android.net.Uri;
import android.os.Build;
+import android.support.v4.content.FileProvider;
+import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
import android.view.Display;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
import com.dimowner.audiorecorder.ARApplication;
+import com.dimowner.audiorecorder.R;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
+import java.text.DecimalFormat;
import timber.log.Timber;
@@ -107,6 +127,38 @@ public static int getStatusBarHeight(Context context) {
return result;
}
+ // A method to find height of the navigation bar
+ public static int getNavigationBarHeight(Context context) {
+ int result = 0;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ try {
+ if (hasNavBar(context)) {
+ int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
+ if (resourceId > 0) {
+ result = context.getResources().getDimensionPixelSize(resourceId);
+ }
+ }
+ } catch (Resources.NotFoundException e) {
+ Timber.e(e);
+ return 0;
+ }
+ }
+ return result;
+ }
+
+ //This method works not correctly
+ public static boolean hasNavBar (Context context) {
+// int id = context.getResources().getIdentifier("config_showNavigationBar", "bool", "android");
+// return id > 0 && context.getResources().getBoolean(id);
+// boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
+// boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
+// return !hasMenuKey && !hasBackKey;
+
+ boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
+ boolean hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME);
+ return !hasHomeKey && !hasBackKey;
+ }
+
public static void setTranslucent(Activity activity, boolean translucent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Window w = activity.getWindow();
@@ -212,4 +264,144 @@ public static long readRecordDuration(File file) {
}
return -1;
}
+
+ /**
+ * Moves icons from the PopupMenu MenuItems' icon fields into the menu title as a Spannable with the icon and title text.
+ */
+ public static void insertMenuItemIcons(Context context, PopupMenu popupMenu) {
+ Menu menu = popupMenu.getMenu();
+ if (hasIcon(menu)) {
+ for (int i = 0; i < menu.size(); i++) {
+ insertMenuItemIcon(context, menu.getItem(i));
+ }
+ }
+ }
+
+ /**
+ * @return true if the menu has at least one MenuItem with an icon.
+ */
+ private static boolean hasIcon(Menu menu) {
+ for (int i = 0; i < menu.size(); i++) {
+ if (menu.getItem(i).getIcon() != null) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Converts the given MenuItem title into a Spannable containing both its icon and title.
+ */
+ private static void insertMenuItemIcon(Context context, MenuItem menuItem) {
+ Drawable icon = menuItem.getIcon();
+
+ // If there no icon, we insert a transparent one to keep the title aligned with the items
+ // which do have icons.
+ if (icon == null) icon = new ColorDrawable(Color.TRANSPARENT);
+
+ int iconSize = context.getResources().getDimensionPixelSize(R.dimen.menu_item_icon_size);
+ icon.setBounds(0, 0, iconSize, iconSize);
+ ImageSpan imageSpan = new ImageSpan(icon);
+
+ // Add a space placeholder for the icon, before the title.
+ SpannableStringBuilder ssb = new SpannableStringBuilder(" " + menuItem.getTitle());
+
+ // Replace the space placeholder with the icon.
+ ssb.setSpan(imageSpan, 1, 2, 0);
+ menuItem.setTitle(ssb);
+ // Set the icon to null just in case, on some weird devices, they've customized Android to display
+ // the icon in the menu... we don't want two icons to appear.
+ menuItem.setIcon(null);
+ }
+
+ public static void shareAudioFile(Context context, String sharePath, String name) {
+ if (sharePath != null) {
+ Uri fileUri = FileProvider.getUriForFile(
+ context,
+ context.getApplicationContext().getPackageName() + ".app_file_provider",
+ new File(sharePath)
+ );
+ Intent share = new Intent(Intent.ACTION_SEND);
+ share.setType("audio/*");
+ share.putExtra(Intent.EXTRA_STREAM, fileUri);
+ share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ Intent chooser = Intent.createChooser(share, context.getResources().getString(R.string.share_record, name));
+ chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(chooser);
+ } else {
+ Timber.e("There no record selected!");
+ Toast.makeText(context, R.string.please_select_record_to_share, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ public static void openAudioFile(Context context, String sharePath, String name) {
+ if (sharePath != null) {
+ Uri fileUri = FileProvider.getUriForFile(
+ context,
+ context.getApplicationContext().getPackageName() + ".app_file_provider",
+ new File(sharePath)
+ );
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(fileUri, "audio/*");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ Intent chooser = Intent.createChooser(intent, context.getResources().getString(R.string.open_record_with, name));
+ chooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(chooser);
+ } else {
+ Timber.e("There no record selected!");
+ Toast.makeText(context, R.string.error_unknown, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ public static void showDialog(Activity activity, int resTitle, int resContent,
+ View.OnClickListener positiveBtnListener, View.OnClickListener negativeBtnListener){
+ showDialog(activity, -1, -1, resTitle, resContent, positiveBtnListener, negativeBtnListener);
+ }
+
+ public static void showDialog(Activity activity, int positiveBtnTextRes, int negativeBtnTextRes, int resTitle, int resContent,
+ final View.OnClickListener positiveBtnListener, final View.OnClickListener negativeBtnListener){
+ final Dialog dialog = new Dialog(activity);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setCancelable(false);
+ View view = activity.getLayoutInflater().inflate(R.layout.dialog_layout, null, false);
+ ((TextView)view.findViewById(R.id.dialog_title)).setText(resTitle);
+ ((TextView)view.findViewById(R.id.dialog_content)).setText(resContent);
+ if (negativeBtnListener != null) {
+ Button negativeBtn = view.findViewById(R.id.dialog_negative_btn);
+ if (negativeBtnTextRes >=0) {
+ negativeBtn.setText(negativeBtnTextRes);
+ }
+ negativeBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ negativeBtnListener.onClick(v);
+ dialog.dismiss();
+ }
+ });
+ } else {
+ view.findViewById(R.id.dialog_negative_btn).setVisibility(View.GONE);
+ }
+ if (positiveBtnListener != null) {
+ Button positiveBtn = view.findViewById(R.id.dialog_positive_btn);
+ if (positiveBtnTextRes >=0) {
+ positiveBtn.setText(positiveBtnTextRes);
+ }
+ positiveBtn.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ positiveBtnListener.onClick(v);
+ dialog.dismiss();
+ }
+ });
+ } else {
+ view.findViewById(R.id.dialog_positive_btn).setVisibility(View.GONE);
+ }
+ dialog.setContentView(view);
+ dialog.show();
+ }
+
+ public static String formatSize(long size) {
+ DecimalFormat formatter = new DecimalFormat("#.##");
+ return formatter.format((float)size/(1024*1024)) + " Mb";
+ }
}
diff --git a/app/src/main/java/com/dimowner/audiorecorder/util/FileUtil.java b/app/src/main/java/com/dimowner/audiorecorder/util/FileUtil.java
index 102d8a0a..eb471a10 100755
--- a/app/src/main/java/com/dimowner/audiorecorder/util/FileUtil.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/util/FileUtil.java
@@ -139,6 +139,38 @@ public static boolean copyFile(FileDescriptor fileToCopy, File newFile) throws I
}
}
+ /**
+ * Copy file.
+ * @param fileToCopy File to copy.
+ * @param newFile File in which will contain copied data.
+ * @return true if copy succeed, otherwise - false.
+ */
+ public static boolean copyFile(File fileToCopy, File newFile) throws IOException {
+ Timber.v("copyFile toCOpy = " + fileToCopy.getAbsolutePath() + " newFile = " + newFile.getAbsolutePath());
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ in = new FileInputStream(fileToCopy);
+ out = new FileOutputStream(newFile);
+
+ if (copyLarge(in, out) > 0) {
+ return true;
+ } else {
+ Timber.e("Nothing was copied!");
+ return false;
+ }
+ } catch (Exception e) {
+ return false;
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
/**
* Get free space for specified file
* @param f Dir
@@ -287,6 +319,11 @@ public static boolean isExternalStorageReadable() {
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state));
}
+ public static boolean isFileInExternalStorage(String path) {
+ String external = Environment.getExternalStorageDirectory().getAbsolutePath();
+ return path.contains(external);
+ }
+
public static File getPublicMusicStorageDir(String albumName) {
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MUSIC), albumName);
diff --git a/app/src/main/java/com/dimowner/audiorecorder/util/TimeUtils.java b/app/src/main/java/com/dimowner/audiorecorder/util/TimeUtils.java
index 8a580c3a..017a017b 100755
--- a/app/src/main/java/com/dimowner/audiorecorder/util/TimeUtils.java
+++ b/app/src/main/java/com/dimowner/audiorecorder/util/TimeUtils.java
@@ -71,7 +71,7 @@ public static String formatTimeIntervalHourMinSec2(long length) {
if (numHour == 0) {
return String.format(Locale.getDefault(), "%02d:%02d", numMinutes, numSeconds % 60);
} else {
- return String.format(Locale.getDefault(), "%02d:%02d:%02d", numHour, numMinutes, numSeconds % 60);
+ return String.format(Locale.getDefault(), "%02d:%02d:%02d", numHour, numMinutes % 60, numSeconds % 60);
}
}
diff --git a/app/src/main/res/drawable/ic_access_time.xml b/app/src/main/res/drawable/ic_access_time.xml
new file mode 100644
index 00000000..ae994688
--- /dev/null
+++ b/app/src/main/res/drawable/ic_access_time.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_calendar_today.xml b/app/src/main/res/drawable/ic_calendar_today.xml
new file mode 100644
index 00000000..f267d069
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar_today.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml
new file mode 100644
index 00000000..650f3807
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_more_vert.xml b/app/src/main/res/drawable/ic_more_vert.xml
new file mode 100644
index 00000000..79db2ee7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more_vert.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_more_vert_transparent.xml b/app/src/main/res/drawable/ic_more_vert_transparent.xml
new file mode 100644
index 00000000..5de1a38b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more_vert_transparent.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_open_with.xml b/app/src/main/res/drawable/ic_open_with.xml
new file mode 100644
index 00000000..78001ed2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_open_with.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_pencil.xml b/app/src/main/res/drawable/ic_pencil.xml
index 73ed8d58..881a2bde 100644
--- a/app/src/main/res/drawable/ic_pencil.xml
+++ b/app/src/main/res/drawable/ic_pencil.xml
@@ -1,9 +1,9 @@
diff --git a/app/src/main/res/drawable/ic_pencil_small.xml b/app/src/main/res/drawable/ic_pencil_small.xml
new file mode 100644
index 00000000..73ed8d58
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pencil_small.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_save_alt.xml b/app/src/main/res/drawable/ic_save_alt.xml
new file mode 100644
index 00000000..5ae0dcb4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_save_alt.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_sort.xml b/app/src/main/res/drawable/ic_sort.xml
new file mode 100644
index 00000000..43061183
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sort.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_sort_by_alpha.xml b/app/src/main/res/drawable/ic_sort_by_alpha.xml
new file mode 100644
index 00000000..755bdb9e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sort_by_alpha.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/raised_button_background.xml b/app/src/main/res/drawable/raised_button_background.xml
new file mode 100644
index 00000000..14ec274d
--- /dev/null
+++ b/app/src/main/res/drawable/raised_button_background.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_info.xml b/app/src/main/res/layout/activity_info.xml
new file mode 100644
index 00000000..edc99da0
--- /dev/null
+++ b/app/src/main/res/layout/activity_info.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 0d4ba76d..7d99746b 100755
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -35,7 +35,7 @@
android:layout_height="wrap_content"
android:contentDescription="@null"
android:layout_gravity="start"
- android:background="?android:selectableItemBackground"
+ android:background="?android:selectableItemBackgroundBorderless"
android:padding="@dimen/spacing_normal"
android:src="@drawable/ic_import"/>
@@ -62,7 +62,7 @@
android:contentDescription="@null"
android:layout_gravity="end"
android:scaleType="center"
- android:src="@drawable/ic_share"/>
+ android:src="@drawable/ic_more_vert"/>
@@ -210,7 +210,7 @@
android:textColor="@color/text_primary_light"
android:maxLines="1"
android:textSize="@dimen/text_large"
- android:drawableEnd="@drawable/ic_pencil"
+ android:drawableEnd="@drawable/ic_pencil_small"
android:visibility="invisible"
tools:text="@string/app_name"
/>
diff --git a/app/src/main/res/layout/activity_records.xml b/app/src/main/res/layout/activity_records.xml
index 8343df22..274221e6 100644
--- a/app/src/main/res/layout/activity_records.xml
+++ b/app/src/main/res/layout/activity_records.xml
@@ -38,34 +38,62 @@
android:layout_height="wrap_content"
android:contentDescription="@null"
android:layout_gravity="start"
- android:background="?android:selectableItemBackground"
+ android:background="?android:selectableItemBackgroundBorderless"
android:padding="@dimen/spacing_normal"
android:src="@drawable/ic_arrow_back"/>
-
+ android:orientation="vertical">
+
+
+
+
+
@@ -192,7 +220,7 @@
android:textColor="@color/text_primary_light"
android:textSize="@dimen/text_xmedium"
android:maxLines="1"
- android:drawableEnd="@drawable/ic_pencil"
+ android:drawableEnd="@drawable/ic_pencil_small"
tools:text="Record 2321"/>
@@ -38,7 +36,7 @@
android:layout_height="wrap_content"
android:contentDescription="@null"
android:layout_gravity="start"
- android:background="?android:selectableItemBackground"
+ android:background="?android:selectableItemBackgroundBorderless"
android:padding="@dimen/spacing_normal"
android:src="@drawable/ic_arrow_back"/>
@@ -126,6 +124,33 @@
/>
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_layout.xml b/app/src/main/res/layout/dialog_layout.xml
new file mode 100644
index 00000000..acbdddd9
--- /dev/null
+++ b/app/src/main/res/layout/dialog_layout.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/list_item.xml b/app/src/main/res/layout/list_item.xml
index dd3cc7d6..864bf2e1 100644
--- a/app/src/main/res/layout/list_item.xml
+++ b/app/src/main/res/layout/list_item.xml
@@ -49,9 +49,9 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="80dp"
+ android:layout_marginEnd="36dp"
android:layout_gravity="center_vertical">
-
@@ -71,15 +71,14 @@
/>
@@ -109,9 +108,20 @@
tools:text="@string/app_name"
android:layout_gravity="end"
android:paddingStart="@dimen/spacing_tiny"
- android:paddingEnd="@dimen/spacing_normal"
+ android:paddingEnd="@dimen/spacing_xsmall"
android:maxLines="1"/>
+
+
diff --git a/app/src/main/res/menu/menu_more.xml b/app/src/main/res/menu/menu_more.xml
new file mode 100644
index 00000000..f9b0abe0
--- /dev/null
+++ b/app/src/main/res/menu/menu_more.xml
@@ -0,0 +1,31 @@
+
+
diff --git a/app/src/main/res/menu/menu_sort.xml b/app/src/main/res/menu/menu_sort.xml
new file mode 100644
index 00000000..bbd4acdc
--- /dev/null
+++ b/app/src/main/res/menu/menu_sort.xml
@@ -0,0 +1,19 @@
+
+
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 00000000..517a7cea
Binary files /dev/null and b/app/src/main/res/values-bg/strings.xml differ
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index cd114917..65ff5300 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -32,6 +32,24 @@
Обратная связь
Не найдено ни одного почтового клиента
Послать по электронной почте…
+ Открыть %s с помощью
+ Удалить
+ Информация
+ Переименовать
+ Открыть с помощью̷
+ Скачать
+ Поделиться
+ Показывать диалог переименования после завершения записи
+ Запись удалена успешно
+ По дате
+ По имени
+ По длительности
+ Приложению требуется разрешение на запись в публичное хранилище. Если не разрешить, записи будут сохраняться в приватном каталоге приложения
+ Название:
+ Формат:
+ Длительность:
+ Размер:
+ Расположение файла:
Настройки приложения:
Настройки записи:
@@ -43,8 +61,11 @@
Сохранить
Отменить
Да
+ Хорошо
Нет
Играть
+ Запретить
+ Разрешить
Неожиданная ошибка
Не удалось создать файл
@@ -56,6 +77,7 @@
Проигрыватель не может считать файл
Не удалось считать аудио файл
Доступ запрещен
+ Не удалось обработать аудиофайл
Зворотній зв\'язок
Не знайдено жодного поштового клієнта
Надіслати по електронній пошті…
+ Відкрити %s з допомогою
+ Видалити
+ Інформація
+ Перейменувати
+ Відкрити з допомогою̷
+ Скачати
+ Поділитися
+ Показувати діалог перейменування після завершення звукозапису
+ Запис %s видалено успішно
+ По даті
+ По імені
+ По тривалості
+ Додатку потрібно дозвіл на запис в публічне сховище. Якщо не дозволити, записи будуть зберігатися в приватному каталозі додатка
+ Назва:
+ Формат:
+ Тривалість:
+ Розмір:
+ Розташування файлу:
Налаштування додатка:
Налаштування запису:
@@ -43,8 +61,11 @@
Зберегти
Відмінити
Так
+ Добре
Ні
Грати
+ Заборонити
+ Дозволити
Неочікувана помилка
Не вдалося створити файл
@@ -56,6 +77,7 @@
Програвач не може зчитати запис
Не вдалося зчитати аудіо файл
Доступ заборонено
+ Не вдалося опрацювати аудіофайл
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index fd73816f..fe99037d 100755
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -5,11 +5,14 @@
#ccffffff
#b3ffffff
#30ffffff
+ #de000000
+ #8a000000
#eaeaea
#ffffff
#f3f5f7
+ #A1FFFFFF
#e51c23
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 5cf46b09..590a71b8 100755
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -32,4 +32,5 @@
316dp
+ 24dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 37a3f42b..00bc328a 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,6 +32,24 @@
Request or feedback
Not found any Email client
Send by email…
+ Open %s with
+ Delete
+ Information
+ Rename
+ Open with̷
+ Download
+ Share
+ Show renaming dialog after stop recording
+ Record deleted successfully
+ By date
+ By name
+ By duration
+ The app requires permission to write to the public store. If not allowed, records will be stored in the app’s private directory
+ Name:
+ Format:
+ Duration:
+ Size:
+ File location:
App settings:
Recording settings:
@@ -43,8 +61,11 @@
Save
Cancel
Yes
+ Ok
No
Play
+ Deny
+ Allow
Unexpected error
Can\'t create file
@@ -56,6 +77,7 @@
Player can\'t read file
Unable to read sound file
Permission denied
+ Can\'t process waveform
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/build.gradle b/build.gradle
index 6453c31b..22446429 100755
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.2'
+ classpath 'com.android.tools.build:gradle:3.4.1'
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 36f69950..8eca3b8b 100755
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Jan 24 12:38:02 EET 2019
+#Thu Aug 01 21:08:22 EEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip