diff --git a/app/build.gradle b/app/build.gradle
index 17f73686..d2da05b7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -82,6 +82,7 @@ dependencies {
compile 'com.github.ksoichiro:android-observablescrollview:1.5.1'
compile 'asia.ivity.android:drag-sort-listview:1.0'
compile 'de.hdodenhof:circleimageview:1.3.0'
+ compile "com.github.semoncat.seekarc:library:0.1"
compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.squareup.okhttp:okhttp:2.4.0'
diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java
new file mode 100644
index 00000000..094c23f0
--- /dev/null
+++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/SleepTimerDialog.java
@@ -0,0 +1,175 @@
+package com.kabouzeid.gramophone.dialogs;
+
+import android.app.AlarmManager;
+import android.app.Dialog;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.kabouzeid.gramophone.R;
+import com.kabouzeid.gramophone.service.MusicService;
+import com.kabouzeid.gramophone.util.MusicUtil;
+import com.kabouzeid.gramophone.util.PreferenceUtils;
+import com.triggertrap.seekarc.SeekArc;
+
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+
+/**
+ * @author Karim Abou Zeid (kabouzeid)
+ */
+public class SleepTimerDialog extends DialogFragment {
+ @InjectView(R.id.seek_arc)
+ SeekArc seekArc;
+ @InjectView(R.id.timer_display)
+ TextView timerDisplay;
+
+ private int seekArcProgress;
+ private MaterialDialog materialDialog;
+ private TimerUpdater timerUpdater;
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ timerUpdater.cancel();
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ timerUpdater = new TimerUpdater();
+ materialDialog = new MaterialDialog.Builder(getActivity())
+ .title(getActivity().getResources().getString(R.string.action_sleep_timer))
+ .positiveText(R.string.action_set)
+ .callback(new MaterialDialog.ButtonCallback() {
+ @Override
+ public void onPositive(MaterialDialog dialog) {
+ if (getActivity() == null) {
+ return;
+ }
+ final int min = seekArcProgress;
+ PreferenceUtils.getInstance(getActivity()).setLastSleepTimerValue(min);
+
+ PendingIntent pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT);
+
+ final long nextSleepTimerElapsedTime = SystemClock.elapsedRealtime() + min * 60 * 1000;
+ PreferenceUtils.getInstance(getActivity()).setNextSleepTimerElapsedRealtime(nextSleepTimerElapsedTime);
+ AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
+ am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextSleepTimerElapsedTime, pi);
+
+ Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_set, min), Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onNeutral(MaterialDialog dialog) {
+ if (getActivity() == null) {
+ return;
+ }
+ final PendingIntent previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE);
+ if (previous != null) {
+ AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
+ am.cancel(previous);
+ previous.cancel();
+ Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_canceled), Toast.LENGTH_SHORT).show();
+ }
+ }
+ })
+ .showListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialog) {
+ if (makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE) != null) {
+ timerUpdater.start();
+ }
+ }
+ })
+ .customView(R.layout.dialog_sleep_timer, false)
+ .build();
+
+ if (getActivity() == null || materialDialog.getCustomView() == null) {
+ return materialDialog;
+ }
+
+ ButterKnife.inject(this, materialDialog.getCustomView());
+
+ seekArc.post(new Runnable() {
+ @Override
+ public void run() {
+ int width = seekArc.getWidth();
+ int height = seekArc.getHeight();
+ int small = Math.min(width, height);
+
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(seekArc.getLayoutParams());
+ layoutParams.height = small;
+ seekArc.setLayoutParams(layoutParams);
+ }
+ });
+
+ seekArcProgress = PreferenceUtils.getInstance(getActivity()).getLastSleepTimerValue();
+ updateTimeDisplayTime();
+ seekArc.setProgress(seekArcProgress);
+
+ seekArc.setOnSeekArcChangeListener(new SeekArc.OnSeekArcChangeListener() {
+ @Override
+ public void onProgressChanged(SeekArc seekArc, int i, boolean b) {
+ if (i < 1) {
+ seekArc.setProgress(1);
+ return;
+ }
+ seekArcProgress = i;
+ updateTimeDisplayTime();
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekArc seekArc) {
+
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekArc seekArc) {
+
+ }
+ });
+
+ return materialDialog;
+ }
+
+ private void updateTimeDisplayTime() {
+ timerDisplay.setText(seekArcProgress + " min");
+ }
+
+ private PendingIntent makeTimerPendingIntent(int flag) {
+ return PendingIntent.getService(getActivity(), 0, makeTimerIntent(), flag);
+ }
+
+ private Intent makeTimerIntent() {
+ return new Intent(getActivity(), MusicService.class)
+ .setAction(MusicService.ACTION_QUIT);
+ }
+
+ private class TimerUpdater extends CountDownTimer {
+ public TimerUpdater() {
+ super(PreferenceUtils.getInstance(getActivity()).getNextSleepTimerElapsedRealTime() - SystemClock.elapsedRealtime(), 1000);
+ }
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ materialDialog.setActionButton(DialogAction.NEUTRAL, materialDialog.getContext().getString(R.string.cancel_current_timer) + " (" + MusicUtil.getReadableDurationString(millisUntilFinished) + ")");
+ }
+
+ @Override
+ public void onFinish() {
+ materialDialog.setActionButton(DialogAction.NEUTRAL, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/LastAddedLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/LastAddedLoader.java
index 2359ba53..0b0838c2 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/loader/LastAddedLoader.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/loader/LastAddedLoader.java
@@ -20,7 +20,7 @@ public class LastAddedLoader {
public static Cursor makeLastAddedCursor(final Context context) {
long fourWeeksAgo = (System.currentTimeMillis() / 1000) - (4 * 3600 * 24 * 7);
// possible saved timestamp caused by user "clearing" the last added playlist
- long cutoff = PreferenceUtils.getInstance(context).getLastAddedCutOff() / 1000;
+ long cutoff = PreferenceUtils.getInstance(context).getLastAddedCutOffTimestamp() / 1000;
if (cutoff < fourWeeksAgo) {
cutoff = fourWeeksAgo;
}
diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java b/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java
index 3006a8fc..ad3b84cd 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/service/MultiPlayer.java
@@ -210,6 +210,9 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
* @return The duration in milliseconds
*/
public int duration() {
+ if (!mIsInitialized) {
+ return -1;
+ }
try {
return mCurrentMediaPlayer.getDuration();
} catch (IllegalStateException e) {
@@ -223,6 +226,9 @@ public class MultiPlayer implements MediaPlayer.OnErrorListener,
* @return The current position in milliseconds
*/
public int position() {
+ if (!mIsInitialized) {
+ return -1;
+ }
try {
return mCurrentMediaPlayer.getCurrentPosition();
} catch (IllegalStateException e) {
diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java
index 0390555e..048f8325 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java
@@ -27,6 +27,7 @@ import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.songadapter.AlbumSongAdapter;
+import com.kabouzeid.gramophone.dialogs.SleepTimerDialog;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.bitmapblur.StackBlurManager;
import com.kabouzeid.gramophone.interfaces.CabHolder;
@@ -347,6 +348,9 @@ public class AlbumDetailActivity extends AbsFabActivity implements PaletteColorH
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
+ case R.id.action_sleep_timer:
+ new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
+ return true;
case R.id.action_equalizer:
NavigationUtil.openEqualizer(this);
return true;
diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java
index d0d9811a..d853fa25 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java
@@ -33,6 +33,7 @@ import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.ArtistAlbumAdapter;
import com.kabouzeid.gramophone.adapter.songadapter.ArtistSongAdapter;
+import com.kabouzeid.gramophone.dialogs.SleepTimerDialog;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.bitmapblur.StackBlurManager;
import com.kabouzeid.gramophone.interfaces.CabHolder;
@@ -449,6 +450,9 @@ public class ArtistDetailActivity extends AbsFabActivity implements PaletteColor
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
+ case R.id.action_sleep_timer:
+ new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
+ return true;
case R.id.action_equalizer:
NavigationUtil.openEqualizer(this);
return true;
diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java
index 481db105..36b1b470 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java
@@ -37,6 +37,7 @@ import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.PagerAdapter;
import com.kabouzeid.gramophone.dialogs.AboutDialog;
import com.kabouzeid.gramophone.dialogs.CreatePlaylistDialog;
+import com.kabouzeid.gramophone.dialogs.SleepTimerDialog;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.SearchQueryHelper;
import com.kabouzeid.gramophone.interfaces.CabHolder;
@@ -338,6 +339,9 @@ public class MainActivity extends AbsFabActivity
int id = item.getItemId();
switch (id) {
+ case R.id.action_sleep_timer:
+ new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
+ return true;
case R.id.action_equalizer:
NavigationUtil.openEqualizer(this);
return true;
diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java
index 15d71f22..5bddc584 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java
@@ -37,6 +37,7 @@ import com.afollestad.materialdialogs.ThemeSingleton;
import com.afollestad.materialdialogs.util.DialogUtils;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog;
+import com.kabouzeid.gramophone.dialogs.SleepTimerDialog;
import com.kabouzeid.gramophone.dialogs.SongDetailDialog;
import com.kabouzeid.gramophone.dialogs.SongShareDialog;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
@@ -629,6 +630,9 @@ public class MusicControllerActivity extends AbsFabActivity {
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
+ case R.id.action_sleep_timer:
+ new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
+ return true;
case R.id.action_toggle_favorite:
MusicUtil.toggleFavorite(this, song);
if (MusicUtil.isFavorite(this, song)) {
diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java
index b99371f1..972d0b80 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java
@@ -14,6 +14,7 @@ import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.songadapter.AbsPlaylistSongAdapter;
import com.kabouzeid.gramophone.adapter.songadapter.PlaylistSongAdapter;
+import com.kabouzeid.gramophone.dialogs.SleepTimerDialog;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.interfaces.CabHolder;
import com.kabouzeid.gramophone.misc.DragSortRecycler;
@@ -124,6 +125,9 @@ public class PlaylistDetailActivity extends AbsFabActivity implements CabHolder
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
+ case R.id.action_sleep_timer:
+ new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
+ return true;
case R.id.action_shuffle_playlist:
//noinspection unchecked
MusicPlayerRemote.openAndShuffleQueue(this, adapter.getDataSet(), true);
diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtils.java b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtils.java
index 2808cb35..8dc7bfd1 100644
--- a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtils.java
+++ b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtils.java
@@ -44,6 +44,8 @@ public final class PreferenceUtils {
public static final String GAPLESS_PLAYBACK = "gapless_playback";
public static final String LAST_ADDED_CUTOFF_TIMESTAMP = "last_added_cutoff_timestamp";
public static final String ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen";
+ public static final String LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value";
+ public static final String NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time";
private static PreferenceUtils sInstance;
@@ -285,7 +287,7 @@ public final class PreferenceUtils {
return mPreferences.getInt(ALBUM_GRID_COLUMNS_LAND, 3);
}
- public long getLastAddedCutOff() {
+ public long getLastAddedCutOffTimestamp() {
return mPreferences.getLong(LAST_ADDED_CUTOFF_TIMESTAMP, 0L);
}
@@ -295,4 +297,24 @@ public final class PreferenceUtils {
editor.putLong(LAST_ADDED_CUTOFF_TIMESTAMP, timestamp);
editor.commit();
}
+
+ public int getLastSleepTimerValue() {
+ return mPreferences.getInt(LAST_SLEEP_TIMER_VALUE, 30);
+ }
+
+ public void setLastSleepTimerValue(final int value) {
+ final SharedPreferences.Editor editor = mPreferences.edit();
+ editor.putInt(LAST_SLEEP_TIMER_VALUE, value);
+ editor.apply();
+ }
+
+ public long getNextSleepTimerElapsedRealTime() {
+ return mPreferences.getLong(NEXT_SLEEP_TIMER_ELAPSED_REALTIME, -1);
+ }
+
+ public void setNextSleepTimerElapsedRealtime(final long value) {
+ final SharedPreferences.Editor editor = mPreferences.edit();
+ editor.putLong(NEXT_SLEEP_TIMER_ELAPSED_REALTIME, value);
+ editor.apply();
+ }
}
diff --git a/app/src/main/res/drawable-xhdpi/default_album_art.png b/app/src/main/res/drawable-xhdpi/default_album_art.png
deleted file mode 100644
index 1647dde0..00000000
Binary files a/app/src/main/res/drawable-xhdpi/default_album_art.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxxhdpi/default_album_art.png b/app/src/main/res/drawable-xxxhdpi/default_album_art.png
new file mode 100644
index 00000000..baef1699
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/default_album_art.png differ
diff --git a/app/src/main/res/drawable/traditional_slider_thumb_dark.xml b/app/src/main/res/drawable/traditional_slider_thumb_dark.xml
new file mode 100644
index 00000000..943b1d3b
--- /dev/null
+++ b/app/src/main/res/drawable/traditional_slider_thumb_dark.xml
@@ -0,0 +1,8 @@
+
+