From 5d032ee244ec89d6af8985e8dd93487611d85c32 Mon Sep 17 00:00:00 2001 From: "Daniel Kim (Yehun)" Date: Sat, 3 Jan 2015 22:27:28 -0500 Subject: [PATCH] Version 1.0.0 release - Added Open Source Licenses - Added MediaPlayer (PlaybackFragment) and an OnClickListener in RecyclerView to listen to recordings. - Added OnDataBasedChangedListener to check for new items added to the database. - Correctly update RecyclerView after saving a recording - Correctly display the item length and date-added in the RecyclerView --- .../com/danielkim/soundrecorder/DBHelper.java | 86 +++--- .../soundrecorder/RecordingItem.java | 8 +- .../soundrecorder/RecordingService.java | 43 +-- .../adapters/FileViewerAdapter.java | 79 +++-- .../fragments/FileViewerFragment.java | 18 +- .../fragments/LicensesFragment.java | 28 ++ .../fragments/PlaybackFragment.java | 270 ++++++++++++++++++ .../fragments/RecordFragment.java | 4 +- .../listeners/OnDatabaseChangedListener.java | 10 + app/src/main/res/layout/activity_main.xml | 1 + app/src/main/res/layout/card_view.xml | 5 +- app/src/main/res/layout/fragment_licenses.xml | 63 ++++ .../res/layout/fragment_media_playback.xml | 87 ++++++ app/src/main/res/layout/fragment_record.xml | 4 +- app/src/main/res/menu/menu_file_viewer.xml | 7 - app/src/main/res/menu/menu_main.xml | 14 +- app/src/main/res/values-v21/styles.xml | 9 + app/src/main/res/values/licenses.xml | 45 +++ app/src/main/res/values/strings.xml | 18 +- app/src/main/res/values/styles.xml | 1 + 20 files changed, 669 insertions(+), 131 deletions(-) create mode 100644 app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java create mode 100644 app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java create mode 100644 app/src/main/java/com/danielkim/soundrecorder/listeners/OnDatabaseChangedListener.java create mode 100644 app/src/main/res/layout/fragment_licenses.xml create mode 100644 app/src/main/res/layout/fragment_media_playback.xml delete mode 100644 app/src/main/res/menu/menu_file_viewer.xml create mode 100644 app/src/main/res/values/licenses.xml diff --git a/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java b/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java index a4bdfe19..2a7b7dd5 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java +++ b/app/src/main/java/com/danielkim/soundrecorder/DBHelper.java @@ -7,6 +7,8 @@ import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; +import com.danielkim.soundrecorder.listeners.OnDatabaseChangedListener; + import java.util.Comparator; /** @@ -15,6 +17,10 @@ public class DBHelper extends SQLiteOpenHelper { private Context mContext; + private static final String LOG_TAG = "DBHelper"; + + private static OnDatabaseChangedListener mOnDatabaseChangedListener; + public static final String DATABASE_NAME = "saved_recordings.db"; private static final int DATABASE_VERSION = 1; @@ -27,12 +33,6 @@ public static abstract class DBHelperItem implements BaseColumns { public static final String COLUMN_NAME_TIME_ADDED = "time_added"; } - public interface OnDatabaseChangedListener { - void onDatabaseEntryUpdated(); - } - - private OnDatabaseChangedListener mOnDatabaseChangedListener; - private static final String TEXT_TYPE = " TEXT"; private static final String COMMA_SEP = ","; private static final String SQL_CREATE_ENTRIES = @@ -46,18 +46,23 @@ public interface OnDatabaseChangedListener { @SuppressWarnings("unused") private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + DBHelperItem.TABLE_NAME; - public long addRecording(String recordingName, String filePath, long length) { - SQLiteDatabase db = getWritableDatabase(); - ContentValues cv = new ContentValues(); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); - cv.put(DBHelperItem.COLUMN_NAME_RECORDING_LENGTH, length); - cv.put(DBHelperItem.COLUMN_NAME_TIME_ADDED, System.currentTimeMillis()); - long rowId = db.insert(DBHelperItem.TABLE_NAME, null, cv); - if (mOnDatabaseChangedListener != null) - mOnDatabaseChangedListener.onDatabaseEntryUpdated(); + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(SQL_CREATE_ENTRIES); + } - return rowId; + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + + public DBHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + mContext = context; + } + + public static void setOnDatabaseChangedListener(OnDatabaseChangedListener listener) { + mOnDatabaseChangedListener = listener; } public RecordingItem getItemAt(int position) { @@ -87,8 +92,6 @@ public void removeItemWithId(int id) { SQLiteDatabase db = getWritableDatabase(); String[] whereArgs = { String.valueOf(id) }; db.delete(DBHelperItem.TABLE_NAME, "_ID=?", whereArgs); - if (mOnDatabaseChangedListener != null) - mOnDatabaseChangedListener.onDatabaseEntryUpdated(); } public int getCount() { @@ -100,21 +103,6 @@ public int getCount() { return count; } - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(SQL_CREATE_ENTRIES); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - - } - - public DBHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - mContext = context; - } - public Context getContext() { return mContext; } @@ -127,18 +115,33 @@ public int compare(RecordingItem item1, RecordingItem item2) { } } + public long addRecording(String recordingName, String filePath, long length) { + + SQLiteDatabase db = getWritableDatabase(); + ContentValues cv = new ContentValues(); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_FILE_PATH, filePath); + cv.put(DBHelperItem.COLUMN_NAME_RECORDING_LENGTH, length); + cv.put(DBHelperItem.COLUMN_NAME_TIME_ADDED, System.currentTimeMillis()); + long rowId = db.insert(DBHelperItem.TABLE_NAME, null, cv); + + if (mOnDatabaseChangedListener != null) { + mOnDatabaseChangedListener.onNewDatabaseEntryAdded(); + } + + return rowId; + } + public void renameItem(RecordingItem item, String recordingName) { SQLiteDatabase db = getWritableDatabase(); ContentValues cv = new ContentValues(); cv.put(DBHelperItem.COLUMN_NAME_RECORDING_NAME, recordingName); db.update(DBHelperItem.TABLE_NAME, cv, DBHelperItem._ID + "=" + item.getId(), null); - if (mOnDatabaseChangedListener != null) - mOnDatabaseChangedListener.onDatabaseEntryUpdated(); - } - public void setOnDatabaseChangedListener(OnDatabaseChangedListener listener) { - mOnDatabaseChangedListener = listener; + if (mOnDatabaseChangedListener != null) { + mOnDatabaseChangedListener.onDatabaseEntryRenamed(); + } } public long restoreRecording(RecordingItem item) { @@ -150,8 +153,9 @@ public long restoreRecording(RecordingItem item) { cv.put(DBHelperItem.COLUMN_NAME_TIME_ADDED, item.getTime()); cv.put(DBHelperItem._ID, item.getId()); long rowId = db.insert(DBHelperItem.TABLE_NAME, null, cv); - if (mOnDatabaseChangedListener != null) - mOnDatabaseChangedListener.onDatabaseEntryUpdated(); + if (mOnDatabaseChangedListener != null) { + //mOnDatabaseChangedListener.onNewDatabaseEntryAdded(); + } return rowId; } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingItem.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingItem.java index 2d735d1c..5c4b7705 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingItem.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingItem.java @@ -7,10 +7,10 @@ * Created by Daniel on 12/30/2014. */ public class RecordingItem implements Parcelable { - private String mName; - private String mFilePath; - private int mId; - private int mLength; // length of recording + private String mName; // file name + private String mFilePath; //file path + private int mId; //id in database + private int mLength; // length of recording in seconds private long mTime; // date/time of the recording public RecordingItem() diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java index eb6b0349..62210023 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java +++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java @@ -1,25 +1,15 @@ package com.danielkim.soundrecorder; import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; -import android.content.Context; import android.content.Intent; import android.media.MediaRecorder; import android.os.Environment; -import android.os.FileObserver; import android.os.IBinder; import android.support.v4.app.NotificationCompat; -import android.support.v4.app.TaskStackBuilder; -import android.support.v7.widget.RecyclerView; import android.util.Log; import android.widget.Toast; -import com.danielkim.soundrecorder.activities.MainActivity; -import com.danielkim.soundrecorder.adapters.FileViewerAdapter; -import com.danielkim.soundrecorder.fragments.FileViewerFragment; - import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; @@ -43,7 +33,8 @@ public class RecordingService extends Service { private DBHelper mDatabase; - private int mElapsedSeconds = 0; + private long mStartingTimeMillis = 0; + private int mElapsedMillis = 0; private static final SimpleDateFormat mTimerFormat = new SimpleDateFormat("mm:ss", Locale.getDefault()); private Timer mTimer = null; @@ -63,24 +54,9 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { startRecording(); - startTimer(); return START_STICKY; } - private void startTimer() { - mTimer = new Timer(); - mIncrementTimerTask = new TimerTask() { - @Override - public void run() { - mElapsedSeconds++; - // NotificationManager mgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - // mgr.notify(1, createNotification()); - } - }; - - mTimer.scheduleAtFixedRate(mIncrementTimerTask, 1000, 1000); - } - @Override public void onDestroy() { if (mRecorder != null) { @@ -107,6 +83,7 @@ public void startRecording() { try { mRecorder.prepare(); mRecorder.start(); + mStartingTimeMillis = System.currentTimeMillis(); } catch (IOException e) { Log.e(LOG_TAG, "prepare() failed"); @@ -115,30 +92,26 @@ public void startRecording() { public void stopRecording() { mRecorder.stop(); + mElapsedMillis = (int) (System.currentTimeMillis() - mStartingTimeMillis); mRecorder.release(); - Toast.makeText(this, R.string.toast_recording_finish + " " + mFilePath, Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.toast_recording_finish) + " " + mFilePath, Toast.LENGTH_LONG).show(); + mRecorder = null; try { - mDatabase.addRecording(mFileName, mFilePath, mElapsedSeconds); - FileViewerAdapter adapter = new FileViewerAdapter(getApplicationContext()); - RecyclerView view = new RecyclerView(getApplicationContext()); - view.swapAdapter(adapter, true); - - //add the new file to the top of the list (position 0) - adapter.notifyItemInserted(0); + mDatabase.addRecording(mFileName, mFilePath, mElapsedMillis); } catch (Exception e){ Log.e(LOG_TAG, "exception", e); } } + //TODO: add timer private Notification createNotification() { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext()) .setSmallIcon(R.drawable.ic_mic_white_36dp) .setContentTitle(getString(R.string.notification_recording)) - .setContentText(mTimerFormat.format(1000 * mElapsedSeconds)) .setOngoing(true); return mBuilder.build(); diff --git a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java index f2d1481c..f72e22cf 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java +++ b/app/src/main/java/com/danielkim/soundrecorder/adapters/FileViewerAdapter.java @@ -3,39 +3,51 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.support.v7.widget.CardView; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; import android.widget.TextView; import android.widget.Toast; import com.danielkim.soundrecorder.DBHelper; import com.danielkim.soundrecorder.R; import com.danielkim.soundrecorder.RecordingItem; +import com.danielkim.soundrecorder.fragments.PlaybackFragment; +import com.danielkim.soundrecorder.listeners.OnDatabaseChangedListener; import java.io.File; import java.text.SimpleDateFormat; import java.util.Locale; -import java.util.TooManyListenersException; /** * Created by Daniel on 12/29/2014. */ -public class FileViewerAdapter extends RecyclerView.Adapter{ +public class FileViewerAdapter extends RecyclerView.Adapter + implements OnDatabaseChangedListener{ private static final String LOG_TAG = "FileViewerAdapter"; - private DBHelper db; - private static final SimpleDateFormat mDateAddedFormatter = new SimpleDateFormat("MMM dd, yyyy - hh:mm a", Locale.getDefault()); + private DBHelper mDatabase; + private static final SimpleDateFormat mDateAddedFormatter = + new SimpleDateFormat("MMM dd, yyyy - hh:mm a", Locale.getDefault()); private static final SimpleDateFormat mLengthFormatter = new SimpleDateFormat("mm:ss", Locale.getDefault()); RecordingItem item; Context mContext; + LinearLayoutManager llm; + + public FileViewerAdapter(Context context, LinearLayoutManager linearLayoutManager) { + super(); + mContext = context; + mDatabase = new DBHelper(mContext); + mDatabase.setOnDatabaseChangedListener(this); + llm = linearLayoutManager; + } @Override public void onBindViewHolder(final RecordingsViewHolder holder, int position) { @@ -44,7 +56,26 @@ public void onBindViewHolder(final RecordingsViewHolder holder, int position) { holder.vName.setText(item.getName()); holder.vLength.setText(mLengthFormatter.format(item.getLength())); - holder.vDateAdded.setText(mDateAddedFormatter.format((int)item.getTime())); + holder.vDateAdded.setText(mDateAddedFormatter.format(item.getTime())); + + // define an on click listener to open PlaybackFragment + holder.cardView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + try { + PlaybackFragment playbackFragment = + new PlaybackFragment().newInstance(getItem(holder.getPosition())); + FragmentTransaction transaction = ((FragmentActivity) mContext) + .getSupportFragmentManager() + .beginTransaction(); + + playbackFragment.show(transaction, "dialog_playback"); + + } catch (Exception e) { + Log.e(LOG_TAG, "exception", e); + } + } + }); holder.cardView.setOnLongClickListener(new View.OnLongClickListener() { @Override @@ -52,8 +83,8 @@ public boolean onLongClick(View v) { // File delete confirm AlertDialog.Builder confirmDelete = new AlertDialog.Builder(mContext); - confirmDelete.setTitle("Confirm Delete..."); - confirmDelete.setMessage("Are you sure you would like to delete this file?"); + confirmDelete.setTitle(mContext.getString(R.string.dialog_title_delete)); + confirmDelete.setMessage(mContext.getString(R.string.dialog_text_delete)); confirmDelete.setCancelable(true); confirmDelete.setPositiveButton("Yes", new DialogInterface.OnClickListener() { @@ -87,7 +118,7 @@ public void onClick(DialogInterface dialog, int id) { @Override public RecordingsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - final View itemView = LayoutInflater. + View itemView = LayoutInflater. from(parent.getContext()). inflate(R.layout.card_view, parent, false); @@ -113,17 +144,24 @@ public RecordingsViewHolder(View v) { @Override public int getItemCount() { - return db.getCount(); + return mDatabase.getCount(); } public RecordingItem getItem(int position) { - return db.getItemAt(position); + return mDatabase.getItemAt(position); } - public FileViewerAdapter(Context context) { - super(); - db = new DBHelper(context); - mContext = context; + @Override + public void onNewDatabaseEntryAdded() { + //item added to top of the list + notifyItemInserted(getItemCount() - 1); + llm.scrollToPosition(getItemCount() - 1); + } + + @Override + //TODO + public void onDatabaseEntryRenamed() { + } public void remove(int position) { @@ -136,7 +174,12 @@ public void remove(int position) { Toast.makeText(mContext, getItem(position).getName() + " successfully deleted", Toast.LENGTH_SHORT).show(); - db.removeItemWithId(getItem(position).getId()); + mDatabase.removeItemWithId(getItem(position).getId()); notifyItemRemoved(position); } + + //TODO + public void removeOutOfApp(String filePath) { + //user deletes a saved recording out of the application through another application + } } diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java index 6bf74bdc..bf29e7f0 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/FileViewerFragment.java @@ -1,8 +1,8 @@ package com.danielkim.soundrecorder.fragments; +import android.os.Bundle; import android.os.FileObserver; import android.support.v4.app.Fragment; -import android.os.Bundle; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -48,13 +48,15 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mRecyclerView.setHasFixedSize(true); LinearLayoutManager llm = new LinearLayoutManager(getActivity()); llm.setOrientation(LinearLayoutManager.VERTICAL); + + //newest to oldest order (database stores from oldest to newest) llm.setReverseLayout(true); llm.setStackFromEnd(true); mRecyclerView.setLayoutManager(llm); mRecyclerView.setItemAnimator(new DefaultItemAnimator()); - mFileViewerAdapter = new FileViewerAdapter(getActivity().getApplicationContext()); + mFileViewerAdapter = new FileViewerAdapter(getActivity(), llm); mRecyclerView.setAdapter(mFileViewerAdapter); return v; @@ -69,17 +71,15 @@ public void onEvent(int event, String file) { if(event == FileObserver.DELETE){ // user deletes a recording file out of the app + String filePath = android.os.Environment.getExternalStorageDirectory().toString() + + "/SoundRecorder" + file + "]"; + Log.d(LOG_TAG, "File deleted [" + android.os.Environment.getExternalStorageDirectory().toString() + "/SoundRecorder" + file + "]"); - // remove file from database and recyclerview, if the file is a recording - // in the database - try { - //TODO: REMOVE FILE - } catch (Exception e) { - Log.e(LOG_TAG, "exception", e); - } + // remove file from database and recyclerview + mFileViewerAdapter.removeOutOfApp(filePath); } } }; diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java new file mode 100644 index 00000000..a93dbded --- /dev/null +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/LicensesFragment.java @@ -0,0 +1,28 @@ +package com.danielkim.soundrecorder.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; + +import com.danielkim.soundrecorder.R; + +/** + * Created by Daniel on 1/3/2015. + */ +public class LicensesFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + LayoutInflater dialogInflater = getActivity().getLayoutInflater(); + View openSourceLicensesView = dialogInflater.inflate(R.layout.fragment_licenses, null); + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); + dialogBuilder.setView(openSourceLicensesView) + .setTitle((getString(R.string.dialog_title_licenses))) + .setNeutralButton(android.R.string.ok, null); + + return dialogBuilder.create(); + } +} diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java new file mode 100644 index 00000000..9fda5441 --- /dev/null +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/PlaybackFragment.java @@ -0,0 +1,270 @@ +package com.danielkim.soundrecorder.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.media.MediaPlayer; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.danielkim.soundrecorder.R; +import com.danielkim.soundrecorder.RecordingItem; +import com.melnykov.fab.FloatingActionButton; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Locale; + +/** + * Created by Daniel on 1/1/2015. + */ +public class PlaybackFragment extends DialogFragment{ + + private static final String LOG_TAG = "PlaybackFragment"; + + private static final String ARG_ITEM = "recording_item"; + private RecordingItem item; + + private Handler mHandler = new Handler(); + + private static final SimpleDateFormat mLengthFormatter = new SimpleDateFormat("mm:ss", Locale.getDefault()); + private MediaPlayer mMediaPlayer = null; + + private SeekBar mSeekBar = null; + private FloatingActionButton mPlayButton = null; + private TextView mCurrentProgressTextView = null; + private TextView mFileNameTextView = null; + private TextView mFileLengthTextView = null; + + //stores whether or not the mediaplayer is currently playing audio + private boolean isPlaying = false; + + public PlaybackFragment newInstance(RecordingItem item) { + PlaybackFragment f = new PlaybackFragment(); + Bundle b = new Bundle(); + b.putParcelable(ARG_ITEM, item); + f.setArguments(b); + + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + item = getArguments().getParcelable(ARG_ITEM); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + Dialog dialog = super.onCreateDialog(savedInstanceState); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_media_playback, null); + + mFileNameTextView = (TextView) view.findViewById(R.id.file_name_text_view); + mFileLengthTextView = (TextView) view.findViewById(R.id.file_length_text_view); + mCurrentProgressTextView = (TextView) view.findViewById(R.id.current_progress_text_view); + + mSeekBar = (SeekBar) view.findViewById(R.id.seekbar); + mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if(mMediaPlayer != null && fromUser) { + mMediaPlayer.seekTo(progress); + + } else if (mMediaPlayer == null && fromUser) { + prepareMediaPlayerFromPoint(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if(mMediaPlayer != null) { + // remove message Handler from updating progress bar + mHandler.removeCallbacks(mRunnable); + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (mMediaPlayer != null) { + //mHandler.removeCallbacks(mRunnable); + mMediaPlayer.seekTo(seekBar.getProgress()); + updateSeekBar(); + } + } + }); + + mPlayButton = (FloatingActionButton) view.findViewById(R.id.fab_play); + mPlayButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onPlay(isPlaying); + isPlaying = !isPlaying; + } + }); + + mFileNameTextView.setText(item.getName()); + mFileLengthTextView.setText(mLengthFormatter.format(item.getLength())); + + builder.setView(view); + + // request a window without the title + dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); + + return builder.create(); +} + + @Override + public void onStart() { + super.onStart(); + + //set transparent background + Window window = getDialog().getWindow(); + window.setBackgroundDrawableResource(android.R.color.transparent); + + //disable buttons from dialog + AlertDialog alertDialog = (AlertDialog) getDialog(); + alertDialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(false); + alertDialog.getButton(Dialog.BUTTON_NEGATIVE).setEnabled(false); + alertDialog.getButton(Dialog.BUTTON_NEUTRAL).setEnabled(false); + } + + @Override + public void onPause() { + super.onPause(); + + if (mMediaPlayer != null) { + stopPlaying(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (mMediaPlayer != null) { + stopPlaying(); + } + } + + // Play start/stop + private void onPlay(boolean isPlaying){ + if (!isPlaying) { + //currently MediaPlayer is not playing audio + if(mMediaPlayer == null) { + startPlaying(); //start from beginning + } else { + resumePlaying(); //resume the currently paused MediaPlayer + } + + } else { + //pause the MediaPlayer + pausePlaying(); + } + } + + private void startPlaying() { + mPlayButton.setImageResource(R.drawable.ic_media_pause); + mMediaPlayer = new MediaPlayer(); + + try { + mMediaPlayer.setDataSource(item.getFilePath()); + mMediaPlayer.prepare(); + mSeekBar.setMax(mMediaPlayer.getDuration()); + + mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + mMediaPlayer.start(); + } + }); + } catch (IOException e) { + Log.e(LOG_TAG, "prepare() failed"); + } + + mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + stopPlaying(); + } + }); + + updateSeekBar(); + } + + private void prepareMediaPlayerFromPoint(int progress) { + //set mediaPlayer to start from middle of the audio file + + mMediaPlayer = new MediaPlayer(); + + try { + mMediaPlayer.setDataSource(item.getFilePath()); + mMediaPlayer.prepare(); + mSeekBar.setMax(mMediaPlayer.getDuration()); + mMediaPlayer.seekTo(progress); + + mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + stopPlaying(); + } + }); + + } catch (IOException e) { + Log.e(LOG_TAG, "prepare() failed"); + } + } + + private void pausePlaying() { + mPlayButton.setImageResource(R.drawable.ic_media_play); + mHandler.removeCallbacks(mRunnable); + mMediaPlayer.pause(); + } + + private void resumePlaying() { + mPlayButton.setImageResource(R.drawable.ic_media_pause); + mHandler.removeCallbacks(mRunnable); + mMediaPlayer.start(); + updateSeekBar(); + } + + private void stopPlaying() { + mPlayButton.setImageResource(R.drawable.ic_media_play); + mHandler.removeCallbacks(mRunnable); + mMediaPlayer.stop(); + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + + mSeekBar.setProgress(mSeekBar.getMax()); + isPlaying = !isPlaying; + } + + //updating mSeekBar every second + private Runnable mRunnable = new Runnable() { + @Override + public void run() { + if(mMediaPlayer != null){ + + int mCurrentPosition = mMediaPlayer.getCurrentPosition(); + + mSeekBar.setProgress(mCurrentPosition); + mCurrentProgressTextView.setText(mLengthFormatter.format(mCurrentPosition)); + updateSeekBar(); + } + } + }; + + private void updateSeekBar() { + mHandler.postDelayed(mRunnable, 1000); + } +} diff --git a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java index ed6a32d8..fb0ac50b 100644 --- a/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java +++ b/app/src/main/java/com/danielkim/soundrecorder/fragments/RecordFragment.java @@ -2,9 +2,9 @@ import android.content.Intent; import android.os.Bundle; +import android.os.Environment; import android.os.SystemClock; import android.support.v4.app.Fragment; -import android.os.Environment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -28,7 +28,7 @@ public class RecordFragment extends Fragment { // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String ARG_POSITION = "position"; - private static final String LOG_TAG = "SoundRecorder_RecordFragment"; + private static final String LOG_TAG = "RecordFragment"; private int position; diff --git a/app/src/main/java/com/danielkim/soundrecorder/listeners/OnDatabaseChangedListener.java b/app/src/main/java/com/danielkim/soundrecorder/listeners/OnDatabaseChangedListener.java new file mode 100644 index 00000000..8600190e --- /dev/null +++ b/app/src/main/java/com/danielkim/soundrecorder/listeners/OnDatabaseChangedListener.java @@ -0,0 +1,10 @@ +package com.danielkim.soundrecorder.listeners; + +/** + * Created by Daniel on 1/3/2015. + * Listen for add/rename items in database + */ +public interface OnDatabaseChangedListener{ + void onNewDatabaseEntryAdded(); + void onDatabaseEntryRenamed(); +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index def0d9bb..239626e3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/layout/card_view.xml b/app/src/main/res/layout/card_view.xml index bece8fc9..a5001ef7 100644 --- a/app/src/main/res/layout/card_view.xml +++ b/app/src/main/res/layout/card_view.xml @@ -9,7 +9,10 @@ android:layout_height="75dp" android:layout_gravity="center" android:layout_margin="5dp" - card_view:cardCornerRadius="4dp"> + android:foreground="?android:attr/selectableItemBackground" + android:transitionName="open_mediaplayer" + card_view:cardCornerRadius="4dp" + card_view:cardElevation="3dp"> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_media_playback.xml b/app/src/main/res/layout/fragment_media_playback.xml new file mode 100644 index 00000000..f867943a --- /dev/null +++ b/app/src/main/res/layout/fragment_media_playback.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_record.xml b/app/src/main/res/layout/fragment_record.xml index 0d61bece..0b6c48c3 100644 --- a/app/src/main/res/layout/fragment_record.xml +++ b/app/src/main/res/layout/fragment_record.xml @@ -1,6 +1,6 @@ - - diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index b1cb9081..fbfa2d6a 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -1,6 +1,12 @@ - - + xmlns:tools="http://schemas.android.com/tools" + tools:context=".MainActivity"> + + diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml index 480b3b05..5f9775f7 100644 --- a/app/src/main/res/values-v21/styles.xml +++ b/app/src/main/res/values-v21/styles.xml @@ -2,16 +2,25 @@ \ No newline at end of file diff --git a/app/src/main/res/values/licenses.xml b/app/src/main/res/values/licenses.xml new file mode 100644 index 00000000..c07b8cbf --- /dev/null +++ b/app/src/main/res/values/licenses.xml @@ -0,0 +1,45 @@ + + + Floating Action Button + Oleksandr Melnykov + + The MIT License (MIT) + \n\nCopyright 2014 Oleksandr Melnykov + + \n\nPermission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + \n\nThe above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + \n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + + PagerSlidingTabStrip + Andreas Stuetz + + Copyright 2013 Andreas Stuetz + + \n\nLicensed 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 + + \n\nhttp://www.apache.org/licenses/LICENSE-2.0 + + \n\nUnless 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. + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 301348e7..b98afa1f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,23 +1,25 @@ + Sound Recorder + Licenses Settings - - Open Source Licenses - About - Record Saved Recordings + Recording started Recording saved to - No saved recordings - + Recording... - Hello world! - Hello blank fragment + + Confirm Delete... + Are you sure you would like to delete this file? + Open Source Licenses + + No saved recordings diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d78869c8..9483179f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,5 +1,6 @@