diff --git a/app/build.gradle b/app/build.gradle index 04e129b..afa06da 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "in.arjsna.audiorecorder" minSdkVersion 16 targetSdkVersion 26 - versionCode 3 - versionName "3.0" + versionCode 4 + versionName "3.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -39,10 +39,10 @@ dependencies { androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' - compile 'com.android.support:appcompat-v7:26.0.2' - compile 'com.android.support:cardview-v7:26.0.2' - compile 'com.android.support:recyclerview-v7:26.0.2' - compile 'com.android.support:design:26.0.2' + compile 'com.android.support:appcompat-v7:26.1.0' + compile 'com.android.support:cardview-v7:26.1.0' + compile 'com.android.support:recyclerview-v7:26.1.0' + compile 'com.android.support:design:26.1.0' compile 'io.reactivex.rxjava2:rxjava:2.1.3' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' @@ -59,4 +59,7 @@ dependencies { annotationProcessor 'com.google.dagger:dagger-compiler:2.11' provided 'javax.annotation:jsr250-api:1.0' compile 'javax.inject:javax.inject:1' + + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23a9415..8d612d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ android:theme="@style/Theme.AppCompat.Light.NoActionBar"> diff --git a/app/src/main/java/in/arjsna/audiorecorder/AudioRecorderApp.java b/app/src/main/java/in/arjsna/audiorecorder/AudioRecorderApp.java index 7bdbb85..f9aa92d 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/AudioRecorderApp.java +++ b/app/src/main/java/in/arjsna/audiorecorder/AudioRecorderApp.java @@ -2,6 +2,7 @@ import android.app.Application; import com.orhanobut.hawk.Hawk; +import com.squareup.leakcanary.LeakCanary; import in.arjsna.audiorecorder.di.components.ApplicationComponent; import in.arjsna.audiorecorder.di.components.DaggerApplicationComponent; import in.arjsna.audiorecorder.di.modules.ApplicationModule; @@ -11,6 +12,10 @@ public class AudioRecorderApp extends Application { @Override public void onCreate() { super.onCreate(); + if (LeakCanary.isInAnalyzerProcess(this)) { + return; + } + LeakCanary.install(this); Hawk.init(getApplicationContext()).build(); applicationComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build(); diff --git a/app/src/main/java/in/arjsna/audiorecorder/activities/PlayListActivity.java b/app/src/main/java/in/arjsna/audiorecorder/activities/PlayListActivity.java index c463557..cf6adf5 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/activities/PlayListActivity.java +++ b/app/src/main/java/in/arjsna/audiorecorder/activities/PlayListActivity.java @@ -4,8 +4,8 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import in.arjsna.audiorecorder.R; -import in.arjsna.audiorecorder.playlist.PlayListFragment; import in.arjsna.audiorecorder.mvpbase.BaseActivity; +import in.arjsna.audiorecorder.playlist.PlayListFragment; public class PlayListActivity extends BaseActivity { diff --git a/app/src/main/java/in/arjsna/audiorecorder/activities/SettingsActivity.java b/app/src/main/java/in/arjsna/audiorecorder/activities/SettingsActivity.java index b7145bc..c445f66 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/activities/SettingsActivity.java +++ b/app/src/main/java/in/arjsna/audiorecorder/activities/SettingsActivity.java @@ -5,8 +5,8 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import in.arjsna.audiorecorder.R; -import in.arjsna.audiorecorder.settings.SettingsFragment; import in.arjsna.audiorecorder.mvpbase.BaseActivity; +import in.arjsna.audiorecorder.settings.SettingsFragment; public class SettingsActivity extends BaseActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { diff --git a/app/src/main/java/in/arjsna/audiorecorder/audiorecording/RecordFragment.java b/app/src/main/java/in/arjsna/audiorecorder/audiorecording/RecordFragment.java index 0679908..5b31ecc 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/audiorecording/RecordFragment.java +++ b/app/src/main/java/in/arjsna/audiorecorder/audiorecording/RecordFragment.java @@ -27,8 +27,8 @@ import in.arjsna.audiorecorder.activities.PlayListActivity; import in.arjsna.audiorecorder.activities.SettingsActivity; import in.arjsna.audiorecorder.audiovisualization.GLAudioVisualizationView; -import in.arjsna.audiorecorder.di.qualifiers.ActivityContext; import in.arjsna.audiorecorder.di.components.ActivityComponent; +import in.arjsna.audiorecorder.di.qualifiers.ActivityContext; import in.arjsna.audiorecorder.mvpbase.BaseFragment; import in.arjsna.audiorecorder.recordingservice.AudioRecordService; import in.arjsna.audiorecorder.recordingservice.AudioRecorder; diff --git a/app/src/main/java/in/arjsna/audiorecorder/db/RecordingItem.java b/app/src/main/java/in/arjsna/audiorecorder/db/RecordingItem.java index 4caf93c..beb6cde 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/db/RecordingItem.java +++ b/app/src/main/java/in/arjsna/audiorecorder/db/RecordingItem.java @@ -1,6 +1,7 @@ package in.arjsna.audiorecorder.db; import android.arch.persistence.room.Entity; +import android.arch.persistence.room.Ignore; import android.arch.persistence.room.PrimaryKey; import android.os.Parcel; import android.os.Parcelable; @@ -15,6 +16,14 @@ public class RecordingItem implements Parcelable { private long mLength; // length of recording in seconds private long mTime; // date/time of the recording + @Ignore + public boolean isPlaying = false; + + @Ignore + public boolean isPaused; + @Ignore + public long playProgress; + public RecordingItem() { } diff --git a/app/src/main/java/in/arjsna/audiorecorder/di/components/ActivityComponent.java b/app/src/main/java/in/arjsna/audiorecorder/di/components/ActivityComponent.java index 46f753e..c019982 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/di/components/ActivityComponent.java +++ b/app/src/main/java/in/arjsna/audiorecorder/di/components/ActivityComponent.java @@ -4,9 +4,9 @@ import in.arjsna.audiorecorder.activities.MainActivity; import in.arjsna.audiorecorder.activities.PlayListActivity; import in.arjsna.audiorecorder.activities.SettingsActivity; -import in.arjsna.audiorecorder.di.scopes.ActivityScope; -import in.arjsna.audiorecorder.di.modules.ActivityModule; import in.arjsna.audiorecorder.audiorecording.RecordFragment; +import in.arjsna.audiorecorder.di.modules.ActivityModule; +import in.arjsna.audiorecorder.di.scopes.ActivityScope; import in.arjsna.audiorecorder.playlist.PlayListFragment; @ActivityScope diff --git a/app/src/main/java/in/arjsna/audiorecorder/di/components/ApplicationComponent.java b/app/src/main/java/in/arjsna/audiorecorder/di/components/ApplicationComponent.java index 6dada40..a4922d7 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/di/components/ApplicationComponent.java +++ b/app/src/main/java/in/arjsna/audiorecorder/di/components/ApplicationComponent.java @@ -4,8 +4,8 @@ import dagger.Component; import in.arjsna.audiorecorder.AudioRecorderApp; import in.arjsna.audiorecorder.db.RecordItemDataSource; -import in.arjsna.audiorecorder.di.qualifiers.ApplicationContext; import in.arjsna.audiorecorder.di.modules.ApplicationModule; +import in.arjsna.audiorecorder.di.qualifiers.ApplicationContext; import javax.inject.Singleton; @Singleton diff --git a/app/src/main/java/in/arjsna/audiorecorder/di/components/ServiceComponent.java b/app/src/main/java/in/arjsna/audiorecorder/di/components/ServiceComponent.java index 90eb9c4..15f3bda 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/di/components/ServiceComponent.java +++ b/app/src/main/java/in/arjsna/audiorecorder/di/components/ServiceComponent.java @@ -1,8 +1,8 @@ package in.arjsna.audiorecorder.di.components; import dagger.Component; -import in.arjsna.audiorecorder.di.scopes.ServiceScope; import in.arjsna.audiorecorder.di.modules.ServiceModule; +import in.arjsna.audiorecorder.di.scopes.ServiceScope; import in.arjsna.audiorecorder.recordingservice.AudioRecordService; @ServiceScope diff --git a/app/src/main/java/in/arjsna/audiorecorder/di/modules/ActivityModule.java b/app/src/main/java/in/arjsna/audiorecorder/di/modules/ActivityModule.java index b9a129d..af55d9c 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/di/modules/ActivityModule.java +++ b/app/src/main/java/in/arjsna/audiorecorder/di/modules/ActivityModule.java @@ -4,7 +4,6 @@ import android.support.v7.widget.LinearLayoutManager; import dagger.Module; import dagger.Provides; -import in.arjsna.audiorecorder.playlist.PlayListAdapter; import in.arjsna.audiorecorder.audiorecording.AudioRecordMVPView; import in.arjsna.audiorecorder.audiorecording.AudioRecordPresenter; import in.arjsna.audiorecorder.audiorecording.AudioRecordPresenterImpl; @@ -14,7 +13,6 @@ import in.arjsna.audiorecorder.playlist.PlayListPresenter; import in.arjsna.audiorecorder.playlist.PlayListPresenterImpl; import io.reactivex.disposables.CompositeDisposable; -import java.util.ArrayList; @Module public class ActivityModule { @@ -43,12 +41,6 @@ LinearLayoutManager provideLinearLayoutManager(@ActivityContext AppCompatActivit return new LinearLayoutManager(context); } - @Provides - @ActivityScope - PlayListAdapter providesPlayListAdapter(@ActivityContext AppCompatActivity context) { - return new PlayListAdapter(context, new ArrayList<>()); - } - @Provides @ActivityScope AudioRecordPresenter provideAudioRecordPresenter( diff --git a/app/src/main/java/in/arjsna/audiorecorder/libs/FillSeekBar.java b/app/src/main/java/in/arjsna/audiorecorder/libs/FillSeekBar.java new file mode 100644 index 0000000..82ee68c --- /dev/null +++ b/app/src/main/java/in/arjsna/audiorecorder/libs/FillSeekBar.java @@ -0,0 +1,89 @@ +package in.arjsna.audiorecorder.libs; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import in.arjsna.audiorecorder.R; +import in.arjsna.audiorecorder.theme.ThemedActivity; + +public class FillSeekBar extends FrameLayout { + private final int mFillColor; + private long mProgress = 0; + private Solid mSolid; + + private final int DEFAULT_FILL_COLOR = Color.WHITE; + private double mMaxValue = 1.0; + + public FillSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + //load styled attributes. + final TypedArray attributes = context.getTheme() + .obtainStyledAttributes(attrs, R.styleable.FillSeekBar, R.attr.fillseekbarViewStyle, 0); + mFillColor = + ((ThemedActivity) context).getPrimaryColor(); + mProgress = attributes.getInt(R.styleable.FillSeekBar_progress, 0); + attributes.recycle(); + mSolid = new Solid(context, null); + mSolid.initPaint(mFillColor); + addView(mSolid, 0, LayoutParams.MATCH_PARENT); + } + + public void setMaxVal(double maxVal) { + this.mMaxValue = maxVal; + } + + public void setProgress(long progress) { + mProgress = progress > mMaxValue ? (long) mMaxValue : progress; + computeProgressRight(); + } + + private void computeProgressRight() { + int mSolidRight = (int) (getWidth() * (1f - mProgress / mMaxValue)); + //Log.i("Stats ", mSolidRight + " " + mProgress); + ViewGroup.LayoutParams params = mSolid.getLayoutParams(); + if (params != null) { + ((LayoutParams) params).width = getWidth() - mSolidRight; + } + mSolid.setLayoutParams(params); + } + + private static class Solid extends View { + + private Paint progressPaint; + + public Solid(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public Solid(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + params.weight = 1; + setLayoutParams(params); + } + + public void initPaint(int mFillColor) { + progressPaint = new Paint(); + progressPaint.setColor(mFillColor); + progressPaint.setAlpha(125); + progressPaint.setStyle(Paint.Style.FILL); + progressPaint.setAntiAlias(true); + } + + @Override protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + //Log.i("Statsinneer ", getRight() + " "); + canvas.drawRect(getLeft(), 0, getWidth(), getBottom(), progressPaint); + } + } +} diff --git a/app/src/main/java/in/arjsna/audiorecorder/mvpbase/IMVPPresenter.java b/app/src/main/java/in/arjsna/audiorecorder/mvpbase/IMVPPresenter.java index b920bd4..2495aff 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/mvpbase/IMVPPresenter.java +++ b/app/src/main/java/in/arjsna/audiorecorder/mvpbase/IMVPPresenter.java @@ -2,5 +2,6 @@ public interface IMVPPresenter { void onAttach(V view); + void onDetach(); } diff --git a/app/src/main/java/in/arjsna/audiorecorder/playback/PlaybackFragment.java b/app/src/main/java/in/arjsna/audiorecorder/playback/PlaybackFragment.java index d8f11da..e29992b 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/playback/PlaybackFragment.java +++ b/app/src/main/java/in/arjsna/audiorecorder/playback/PlaybackFragment.java @@ -265,6 +265,7 @@ private void stopPlaying() { if (mMediaPlayer != null) { int mCurrentPosition = mMediaPlayer.getCurrentPosition(); + Log.i("Seekbar", " " + mCurrentPosition); mSeekBar.setProgress(mCurrentPosition); long minutes = TimeUnit.MILLISECONDS.toMinutes(mCurrentPosition); diff --git a/app/src/main/java/in/arjsna/audiorecorder/playlist/ListItemEventsListener.java b/app/src/main/java/in/arjsna/audiorecorder/playlist/ListItemEventsListener.java deleted file mode 100644 index f2b04c2..0000000 --- a/app/src/main/java/in/arjsna/audiorecorder/playlist/ListItemEventsListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package in.arjsna.audiorecorder.playlist; - -import in.arjsna.audiorecorder.db.RecordingItem; - -interface ListItemEventsListener { - void onItemLongClick(int position, RecordingItem recordingItem); - - void onItemClick(int position, RecordingItem recordingItem); -} diff --git a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListAdapter.java b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListAdapter.java index 5e29dd8..2698f24 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListAdapter.java +++ b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListAdapter.java @@ -1,17 +1,20 @@ package in.arjsna.audiorecorder.playlist; import android.content.Context; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.RecyclerView; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import in.arjsna.audiorecorder.R; -import in.arjsna.audiorecorder.db.RecordItemDataSource; import in.arjsna.audiorecorder.db.RecordingItem; -import java.util.ArrayList; +import in.arjsna.audiorecorder.di.qualifiers.ActivityContext; +import in.arjsna.audiorecorder.libs.FillSeekBar; import java.util.concurrent.TimeUnit; +import javax.inject.Inject; public class PlayListAdapter extends RecyclerView.Adapter { @@ -20,23 +23,24 @@ public class PlayListAdapter extends RecyclerView.Adapter recordingItems; - private PlayListFragment listItemEventsListener; + private final PlayListPresenter playListPresenter; - public PlayListAdapter(Context context, ArrayList recordingItems) { + @Inject + public PlayListAdapter(@ActivityContext AppCompatActivity context, + PlayListPresenter playListPresenter) { mContext = context; - this.recordingItems = recordingItems; + this.playListPresenter = playListPresenter; inflater = LayoutInflater.from(mContext); } @Override public void onBindViewHolder(final RecordingsViewHolder holder, int position) { - RecordingItem currentRecording = recordingItems.get(position); + RecordingItem currentRecording = playListPresenter.getListItemAt(position); long itemDuration = currentRecording.getLength(); long minutes = TimeUnit.MILLISECONDS.toMinutes(itemDuration); long seconds = TimeUnit.MILLISECONDS.toSeconds(itemDuration) - TimeUnit.MINUTES.toSeconds(minutes); - + holder.fillSeekBar.setMaxVal(itemDuration); holder.vName.setText(currentRecording.getName()); holder.vLength.setText( String.format(mContext.getString(R.string.play_time_format), minutes, seconds)); @@ -45,52 +49,71 @@ public PlayListAdapter(Context context, ArrayList recordingItems) | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR)); - - holder.cardView.setOnClickListener(view -> listItemEventsListener.onItemClick(position, currentRecording)); - - holder.cardView.setOnLongClickListener(v -> { - listItemEventsListener.onItemLongClick(position, currentRecording); - return false; - }); + holder.fillSeekBar.setProgress(currentRecording.playProgress); + holder.playStateImage.setImageResource( + currentRecording.isPlaying ? R.drawable.ic_pause_grey : R.drawable.ic_play_arrow_grey); } @Override public RecordingsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View itemView = inflater. - inflate(R.layout.card_view, parent, false); - return new RecordingsViewHolder(itemView); - } - - void addAllAndNotify(ArrayList recordingItems) { - this.recordingItems.addAll(recordingItems); - notifyDataSetChanged(); - } - - void setListItemEventsListener(PlayListFragment listItemEventsListener) { - this.listItemEventsListener = listItemEventsListener; - } - - void removeItemAndNotify(int position) { - recordingItems.remove(position); - notifyItemRemoved(position); + return new RecordingsViewHolder(inflater. + inflate(R.layout.record_list_item, parent, false), playListPresenter); } static class RecordingsViewHolder extends RecyclerView.ViewHolder { + final ImageView playStateImage; final TextView vName; + final TextView playProgress; final TextView vLength; final TextView vDateAdded; final View cardView; + final FillSeekBar fillSeekBar; + private final PlayListPresenter playListPresenter; - RecordingsViewHolder(View v) { + RecordingsViewHolder(View v, PlayListPresenter playListPresenter) { super(v); + this.playListPresenter = playListPresenter; + playStateImage = v.findViewById(R.id.record_list_image); vName = v.findViewById(R.id.file_name_text); + playProgress = v.findViewById(R.id.play_progress_text); vLength = v.findViewById(R.id.file_length_text); vDateAdded = v.findViewById(R.id.file_date_added_text); - cardView = v.findViewById(R.id.card_view); + cardView = v.findViewById(R.id.record_item_root_view); + fillSeekBar = v.findViewById(R.id.attached_seek_bar); + bindEvents(); + } + + private void bindEvents() { + cardView.setOnClickListener( + view -> playListPresenter.onListItemClick(getAdapterPosition())); + + cardView.setOnLongClickListener(v -> { + playListPresenter.onListItemLongClick(getAdapterPosition()); + return false; + }); + } + + public void updateProgressInSeekBar(Integer position) { + RecordingItem currentItem = playListPresenter.getListItemAt(position); + fillSeekBar.setProgress(currentItem.playProgress); + } + + public void updatePlayTimer(int position) { + RecordingItem currentItem = playListPresenter.getListItemAt(position); + if (currentItem.playProgress > 0) { + playProgress.setVisibility(View.VISIBLE); + long minutes = TimeUnit.MILLISECONDS.toMinutes(currentItem.playProgress); + long seconds = + TimeUnit.MILLISECONDS.toSeconds(currentItem.playProgress) - TimeUnit.MINUTES.toSeconds( + minutes); + playProgress.setText(String.format("%02d:%02d", minutes, seconds)); + } else { + playProgress.setVisibility(View.GONE); + } } } @Override public int getItemCount() { - return recordingItems.size(); + return playListPresenter.getListItemCount(); } //TODO diff --git a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListFragment.java b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListFragment.java index 3859cd5..00b2543 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListFragment.java +++ b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListFragment.java @@ -1,11 +1,12 @@ package in.arjsna.audiorecorder.playlist; import android.content.Intent; +import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.FileObserver; +import android.os.Handler; import android.support.v7.app.AlertDialog; -import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -18,16 +19,16 @@ import in.arjsna.audiorecorder.R; import in.arjsna.audiorecorder.db.RecordingItem; import in.arjsna.audiorecorder.di.components.ActivityComponent; -import in.arjsna.audiorecorder.playback.PlaybackFragment; import in.arjsna.audiorecorder.mvpbase.BaseFragment; import in.arjsna.audiorecorder.recordingservice.Constants; import in.arjsna.audiorecorder.theme.ThemeHelper; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import javax.inject.Inject; public class PlayListFragment extends BaseFragment - implements PlayListMVPView, ListItemEventsListener { + implements PlayListMVPView { private static final String LOG_TAG = "PlayListFragment"; @Inject @@ -36,8 +37,10 @@ public class PlayListFragment extends BaseFragment public LinearLayoutManager llm; @Inject public PlayListPresenter playListPresenter; + private RecyclerView mRecordingsListView; private TextView emptyListLabel; + private MediaPlayer mMediaPlayer; public static PlayListFragment newInstance() { return new PlayListFragment(); @@ -57,6 +60,7 @@ public void onCreate(Bundle savedInstanceState) { Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_file_viewer, container, false); initViews(v); + mMediaPlayer = new MediaPlayer(); return v; } @@ -71,9 +75,8 @@ private void initViews(View v) { llm.setStackFromEnd(true); mRecordingsListView.setLayoutManager(llm); - mRecordingsListView.setItemAnimator(new DefaultItemAnimator()); + //mRecordingsListView.setItemAnimator(new DefaultItemAnimator()); mRecordingsListView.setAdapter(mPlayListAdapter); - mPlayListAdapter.setListItemEventsListener(this); playListPresenter.onViewInitialised(); } @@ -106,12 +109,12 @@ private void initViews(View v) { } @Override public void onDestroy() { - super.onDestroy(); playListPresenter.onDetach(); + super.onDestroy(); } - @Override public void showData(ArrayList recordingItems) { - mPlayListAdapter.addAllAndNotify(recordingItems); + @Override public void notifyListAdapter() { + mPlayListAdapter.notifyDataSetChanged(); } @Override public void setRecordingListVisible() { @@ -138,6 +141,32 @@ private void initViews(View v) { observer.stopWatching(); } + private int positionOfCurrentViewHolder = -1; + private PlayListAdapter.RecordingsViewHolder recordingsViewHolder; + + Handler uiThreadHandler = new Handler(); + + @Override public void updateProgressInListItem(Integer position) { + if (position != positionOfCurrentViewHolder || recordingsViewHolder == null) { + positionOfCurrentViewHolder = position; + recordingsViewHolder = + (PlayListAdapter.RecordingsViewHolder) mRecordingsListView.findViewHolderForAdapterPosition( + position); + } + if (recordingsViewHolder != null && recordingsViewHolder.getAdapterPosition() == position) { + uiThreadHandler.post(() -> recordingsViewHolder.updateProgressInSeekBar(position)); + } else { + positionOfCurrentViewHolder = -1; + recordingsViewHolder = null; + } + } + + @Override public void updateTimerInListItem(int position) { + if (recordingsViewHolder != null) { + uiThreadHandler.post(() -> recordingsViewHolder.updatePlayTimer(position)); + } + } + @Override public void notifyListItemChange(Integer position) { mPlayListAdapter.notifyItemChanged(position); } @@ -147,10 +176,10 @@ private void initViews(View v) { } @Override public void notifyListItemRemove(Integer position) { - mPlayListAdapter.removeItemAndNotify(position); + mPlayListAdapter.notifyItemRemoved(position); } - @Override public void onItemLongClick(int position, RecordingItem recordingItem) { + @Override public void showFileOptionDialog(int position, RecordingItem recordingItem) { ArrayList fileOptions = new ArrayList<>(); fileOptions.add(getString(R.string.dialog_file_share)); fileOptions.add(getString(R.string.dialog_file_rename)); @@ -164,13 +193,13 @@ private void initViews(View v) { builder.setItems(items, (dialog, listItem) -> { switch (listItem) { case 0: - shareFileDialog(recordingItem); + playListPresenter.shareFileClicked(position); break; case 1: - renameFileDialog(recordingItem, position); + playListPresenter.renameFileClicked(position); break; case 2: - deleteFileDialog(recordingItem, position); + playListPresenter.deleteFileClicked(position); break; } }); @@ -182,16 +211,18 @@ private void initViews(View v) { alert.show(); } - private void shareFileDialog(RecordingItem recordingItem) { + @Override + public void shareFileDialog(String filePath) { Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, - Uri.fromFile(new File(recordingItem.getFilePath()))); + Uri.fromFile(new File(filePath))); shareIntent.setType("audio/mp4"); getActivity().startActivity(Intent.createChooser(shareIntent, getText(R.string.send_to))); } - private void renameFileDialog(final RecordingItem recordingItem, int position) { + @Override + public void showRenameFileDialog(int position) { AlertDialog.Builder renameFileBuilder = new AlertDialog.Builder(getActivity()); View view = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_rename_file, null); final EditText input = view.findViewById(R.id.new_name); @@ -200,7 +231,7 @@ private void renameFileDialog(final RecordingItem recordingItem, int position) { renameFileBuilder.setPositiveButton(getString(R.string.dialog_action_ok), (dialog, id) -> { String value = input.getText().toString().trim() + Constants.AUDIO_RECORDER_FILE_EXT_WAV; - playListPresenter.renameFile(recordingItem, position, value); + playListPresenter.renameFile(position, value); dialog.cancel(); }); renameFileBuilder.setNegativeButton(getActivity().getString(R.string.dialog_action_cancel), @@ -210,14 +241,15 @@ private void renameFileDialog(final RecordingItem recordingItem, int position) { alert.show(); } - private void deleteFileDialog(final RecordingItem recordingItem, int position) { + @Override + public void showDeleteFileDialog(int position) { AlertDialog.Builder confirmDelete = new AlertDialog.Builder(getActivity()); confirmDelete.setTitle(getString(R.string.dialog_title_delete)); confirmDelete.setMessage(getString(R.string.dialog_text_delete)); confirmDelete.setCancelable(true); confirmDelete.setPositiveButton(getString(R.string.dialog_action_yes), (dialog, id) -> { - playListPresenter.deleteFile(recordingItem, position); + playListPresenter.deleteFile(position); dialog.cancel(); }); confirmDelete.setNegativeButton(getString(R.string.dialog_action_no), @@ -226,9 +258,32 @@ private void deleteFileDialog(final RecordingItem recordingItem, int position) { alert.show(); } - @Override public void onItemClick(int position, RecordingItem recordingItem) { - PlaybackFragment playbackFragment = new PlaybackFragment().newInstance(recordingItem); - playbackFragment.show(getActivity().getSupportFragmentManager(), "dialog_playback"); + @Override public void pauseMediaPlayer(int position) { + mMediaPlayer.pause(); + } + + @Override public void resumeMediaPlayer(int position) { + mMediaPlayer.start(); + } + + @Override public void stopMediaPlayer(int currentPlayingItem) { + if (mMediaPlayer != null) { + Log.i("Debug ", "Stopping"); + mMediaPlayer.stop(); + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + } + } + + @Override public void startMediaPlayer(int position, RecordingItem recordingItem) + throws IOException { + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setDataSource(recordingItem.getFilePath()); + mMediaPlayer.prepare(); + mMediaPlayer.setOnPreparedListener(MediaPlayer::start); + mMediaPlayer.setOnCompletionListener(mp -> playListPresenter.mediaPlayerStopped()); + Log.i("Debug ", "Started"); } } diff --git a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListMVPView.java b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListMVPView.java index 52ff586..6cd24ed 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListMVPView.java +++ b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListMVPView.java @@ -2,10 +2,10 @@ import in.arjsna.audiorecorder.db.RecordingItem; import in.arjsna.audiorecorder.mvpbase.IMVPView; -import java.util.ArrayList; +import java.io.IOException; public interface PlayListMVPView extends IMVPView { - void showData(ArrayList recordingItems); + void notifyListAdapter(); void setRecordingListVisible(); @@ -24,4 +24,24 @@ public interface PlayListMVPView extends IMVPView { void showError(String message); void notifyListItemRemove(Integer position); + + void pauseMediaPlayer(int position); + + void stopMediaPlayer(int currentPlayingItem); + + void startMediaPlayer(int position, RecordingItem recordingItem) throws IOException; + + void resumeMediaPlayer(int position); + + void showFileOptionDialog(int position, RecordingItem recordingItem); + + void shareFileDialog(String filePath); + + void showRenameFileDialog(int position); + + void showDeleteFileDialog(int position); + + void updateProgressInListItem(Integer position); + + void updateTimerInListItem(int position); } diff --git a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenter.java b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenter.java index af5d7d5..96f24bf 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenter.java +++ b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenter.java @@ -6,7 +6,23 @@ public interface PlayListPresenter extends IMVPPresenter { void onViewInitialised(); - void renameFile(RecordingItem recordingItem, int position, String value); + void renameFile(int position, String value); - void deleteFile(RecordingItem recordingItem, int position); + void deleteFile(int position); + + RecordingItem getListItemAt(int position); + + void onListItemClick(int position); + + void onListItemLongClick(int position); + + int getListItemCount(); + + void shareFileClicked(int position); + + void renameFileClicked(int position); + + void deleteFileClicked(int position); + + void mediaPlayerStopped(); } diff --git a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenterImpl.java b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenterImpl.java index 89eb14b..e44e4b4 100644 --- a/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenterImpl.java +++ b/app/src/main/java/in/arjsna/audiorecorder/playlist/PlayListPresenterImpl.java @@ -1,25 +1,39 @@ package in.arjsna.audiorecorder.playlist; import android.os.Environment; +import android.util.Log; import in.arjsna.audiorecorder.db.RecordItemDataSource; import in.arjsna.audiorecorder.db.RecordingItem; import in.arjsna.audiorecorder.mvpbase.BasePresenter; +import io.reactivex.Flowable; import io.reactivex.Single; import io.reactivex.SingleObserver; import io.reactivex.SingleOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Function; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subscribers.DisposableSubscriber; import java.io.File; +import java.io.IOException; import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; public class PlayListPresenterImpl extends BasePresenter implements PlayListPresenter { + private static final int INVALID_ITEM = -1; + private static final int PROGRESS_OFFSET = 20; @Inject public RecordItemDataSource recordItemDataSource; + private int currentPlayingItem; + private boolean isAudioPlaying = false; + private boolean isAudioPaused = false; + private List recordingItems = new ArrayList<>(); + @Inject public PlayListPresenterImpl(CompositeDisposable compositeDisposable) { super(compositeDisposable); @@ -30,20 +44,21 @@ public PlayListPresenterImpl(CompositeDisposable compositeDisposable) { getAttachedView().startWatchingForFileChanges(); } - @Override public void renameFile(RecordingItem recordingItem, int adapterPosition, String value) { - rename(recordingItem, adapterPosition, value).subscribe(new SingleObserver() { - @Override public void onSubscribe(Disposable d) { + @Override public void renameFile(int adapterPosition, String value) { + rename(recordingItems.get(adapterPosition), adapterPosition, value).subscribe( + new SingleObserver() { + @Override public void onSubscribe(Disposable d) { - } + } - @Override public void onSuccess(Integer position) { - getAttachedView().notifyListItemChange(position); - } + @Override public void onSuccess(Integer position) { + getAttachedView().notifyListItemChange(position); + } - @Override public void onError(Throwable e) { - getAttachedView().showError(e.getMessage()); - } - }); + @Override public void onError(Throwable e) { + getAttachedView().showError(e.getMessage()); + } + }); } private Single rename(RecordingItem recordingItem, int adapterPosition, String name) { @@ -66,8 +81,8 @@ private Single rename(RecordingItem recordingItem, int adapterPosition, }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); } - @Override public void deleteFile(RecordingItem recordingItem, int position) { - removeFile(recordingItem, position).subscribe(new SingleObserver() { + @Override public void deleteFile(int position) { + removeFile(recordingItems.get(position), position).subscribe(new SingleObserver() { @Override public void onSubscribe(Disposable d) { } @@ -82,11 +97,121 @@ private Single rename(RecordingItem recordingItem, int adapterPosition, }); } + @Override public RecordingItem getListItemAt(int position) { + return recordingItems.get(position); + } + + @Override public void mediaPlayerStopped() { + updateStateToStop(); + } + + private void updateStateToStop() { + if (playProgressDisposable != null) { + playProgressDisposable.dispose(); + } + isAudioPlaying = false; + isAudioPaused = false; + currentProgress = 0; + RecordingItem currentItem = recordingItems.get(currentPlayingItem); + currentItem.isPlaying = false; + currentItem.playProgress = 0; + getAttachedView().notifyListItemChange(currentPlayingItem); + currentPlayingItem = INVALID_ITEM; + } + + @Override public void onListItemClick(int position) { + try { + if (isAudioPlaying) { + if (currentPlayingItem == position) { + if (isAudioPaused) { + isAudioPaused = false; + getAttachedView().resumeMediaPlayer(position); + recordingItems.get(position).isPlaying = true; + updateProgress(position); + } else { + isAudioPaused = true; + getAttachedView().pauseMediaPlayer(position); + recordingItems.get(position).isPlaying = false; + playProgressDisposable.dispose(); + } + } else { + getAttachedView().stopMediaPlayer(currentPlayingItem); + updateStateToStop(); + startPlayer(position); + } + } else { + startPlayer(position); + } + getAttachedView().notifyListItemChange(position); + } catch (IOException e) { + getAttachedView().showError("Failed to start media Player"); + } + } + + private long currentProgress = 0; + private void startPlayer(int position) throws IOException { + isAudioPlaying = true; + currentProgress = 0; + recordingItems.get(position).isPlaying = true; + getAttachedView().startMediaPlayer(position, recordingItems.get(position)); + currentPlayingItem = position; + updateProgress(position); + } + + @Override public void onListItemLongClick(int position) { + getAttachedView().showFileOptionDialog(position, recordingItems.get(position)); + } + + @Override public int getListItemCount() { + return recordingItems.size(); + } + + @Override public void shareFileClicked(int position) { + getAttachedView().shareFileDialog(recordingItems.get(position).getFilePath()); + } + + @Override public void renameFileClicked(int position) { + getAttachedView().showRenameFileDialog(position); + } + + @Override public void deleteFileClicked(int position) { + getAttachedView().showDeleteFileDialog(position); + } + + private DisposableSubscriber playProgressDisposable; + private void updateProgress(int position) { + playProgressDisposable = Flowable.interval(PROGRESS_OFFSET, TimeUnit.MILLISECONDS) + .onBackpressureDrop() + .map(aLong -> { + currentProgress += PROGRESS_OFFSET; + recordingItems.get(position).playProgress = currentProgress; + getAttachedView().updateProgressInListItem(position); + return currentProgress / 1000; + }) + .distinctUntilChanged() + .subscribeOn(Schedulers.computation()) + .subscribeWith(new DisposableSubscriber() { + @Override public void onNext(Long aLong) { + getAttachedView().updateTimerInListItem(position); + } + + @Override public void onError(Throwable t) { + + } + + @Override public void onComplete() { + + } + }); + getCompositeDisposable().add(playProgressDisposable); + } + private Single removeFile(RecordingItem recordingItem, int position) { return Single.create((SingleOnSubscribe) e -> { File file = new File(recordingItem.getFilePath()); if (file.delete()) { recordItemDataSource.deleteRecordItem(recordingItem); + recordingItems.remove(position); e.onSuccess(position); } else { e.onError(new Exception("File deletion failed")); @@ -96,6 +221,7 @@ private Single removeFile(RecordingItem recordingItem, int position) { @Override public void onDetach() { getAttachedView().stopWatchingForFileChanges(); + getAttachedView().stopMediaPlayer(currentPlayingItem); super.onDetach(); } @@ -105,7 +231,8 @@ private void fillAdapter() { .observeOn(AndroidSchedulers.mainThread()) .subscribe((recordingItems) -> { if (recordingItems.size() > 0) { - getAttachedView().showData((ArrayList) recordingItems); + this.recordingItems.addAll(recordingItems); + getAttachedView().notifyListAdapter(); } else { getAttachedView().setRecordingListInVisible(); getAttachedView().setEmptyLabelVisible(); diff --git a/app/src/main/res/drawable-v21/ic_pause_grey.xml b/app/src/main/res/drawable-v21/ic_pause_grey.xml new file mode 100644 index 0000000..b4497a1 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_pause_grey.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_pause_grey.xml b/app/src/main/res/drawable/ic_pause_grey.xml new file mode 100644 index 0000000..b4497a1 --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_grey.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/drawable/ic_play_arrow_grey.xml b/app/src/main/res/drawable/ic_play_arrow_grey.xml new file mode 100644 index 0000000..12ae226 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_arrow_grey.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/layout/card_view.xml b/app/src/main/res/layout/record_list_item.xml similarity index 60% rename from app/src/main/res/layout/card_view.xml rename to app/src/main/res/layout/record_list_item.xml index 1d8a352..e21a5c2 100644 --- a/app/src/main/res/layout/card_view.xml +++ b/app/src/main/res/layout/record_list_item.xml @@ -1,38 +1,38 @@ - - - + app:layout_constraintBottom_toBottomOf="parent"/> + + - - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 25ff2ac..cfa162a 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -25,4 +25,11 @@ + + + + + + + \ No newline at end of file