diff --git a/.gitignore b/.gitignore
index 1edd30fff..8e1707a7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@
/app/app-release.apk
app/app.iml
app/manifest-merger-release-report.txt
+
+secret.xml
+google-services.json
diff --git a/.travis.yml b/.travis.yml
index 4b7ffa368..c69b3554d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,5 @@
language: android
+jdk: oraclejdk8
branches:
only:
@@ -10,6 +11,6 @@ android:
components:
- platform-tools
- tools
- - build-tools-23.0.2
- - android-23
- - extra-android-m2repository
\ No newline at end of file
+ - build-tools-28.0.3
+ - android-28
+ - extra-android-m2repository
diff --git a/Changelog.md b/Changelog.md
index be5dd9b6d..1659804e7 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,4 +1,13 @@
#Changelog
+
+v0.21b-
+
+* Add Homescreen widget
+* View playlists in list, grid and default
+* Delete playlists, clear auto playlists and hide auto playlists
+* Remove songs from playlists, reorder playlists
+* Delete songs from device
+
v0.14b-
* Added Theme engine
diff --git a/README.md b/README.md
index 0d5f9cba7..09c5c3003 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,42 @@
# Timber
[](https://travis-ci.org/naman14/Timber)
-[WIP][BETA]-Material Design Music Player
+Material Design Music Player
-[](https://play.google.com/store/apps/details?id=naman14.timber)
-[](https://f-droid.org/repository/browse/?fdid=naman14.timber)
+This project is no longer in active development. Please refer to [TimberX](https://github.com/naman14/TimberX) instead
-## Screenshots
-
-
-
-
-
-
+
-## Contribute
-### Translations
+
-If there isn't any translations in your language go to the [res folder](https://github.com/naman14/Timber/blob/master/app/src/main/res/) and create a file named "values-XX/strings.xml" where XX is the target language's code. Copy and paste the content of the [English base file](https://github.com/naman14/Timber/blob/master/app/src/main/res/values/strings.xml) and remove all strings with "translatable=false" attribute. Translate all the keys and make a pull request.
+## Screenshots
-If there is a translation in your language go to its folder in [res folder](https://github.com/naman14/Timber/blob/master/app/src/main/res/) and edit the strings.xml file. You'd like to update the translation with new keys by copying them from the [English base file](https://github.com/naman14/Timber/blob/master/app/src/main/res/values/strings.xml). Edit the file and make a pull request.
+
+
+
+
+
+
+
+
+
+
+
+
+## Features
+- Material design
+- Browse Songs, Albums, Artists
+- Create and edit playlists
+- 6 different now playing styles
+- Homescreen widgets
+- Browse device folders
+- Dark theme and UI customisability
+- Gestures for track switching
+- LastFM scrobble
+- Android Wear and Android Auto support
+- Playing queue in notification (Xposed)
+- Lyrics support
+- Chromecast support
## Changelog
@@ -34,7 +51,7 @@ Changelog is available [here](https://github.com/naman14/Timber/blob/master/Chan
* [CircularSeekBar](https://github.com/devadvance/circularseekbar)
* [Nammu](https://github.com/tajchert/Nammu)
-#Donate
+# Donate
Paypal donation email-
namandwivedi14@gmail.com
diff --git a/app/build.gradle b/app/build.gradle
index 68895f1d2..1de8c5417 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,23 +1,27 @@
apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+apply plugin: 'com.google.firebase.crashlytics'
android {
- compileSdkVersion 23
- buildToolsVersion '23.0.2'
+ compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
applicationId "naman14.timber"
- minSdkVersion 16
- targetSdkVersion 23
- versionCode 8
- versionName "0.14b"
+ minSdkVersion rootProject.minSdkVersion
+ targetSdkVersion rootProject.targetSdkVersion
+ versionCode 22
+ versionName "1.8"
//renderscript support mode is not supported for 21+ with gradle version 2.0
- renderscriptTargetApi 20
- renderscriptSupportModeEnabled true
+ renderscriptTargetApi 16
+ renderscriptSupportModeEnabled false
+ multiDexEnabled true
}
+
buildTypes {
release {
- minifyEnabled true
+ minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+
}
debug {
versionNameSuffix "-debug"
@@ -25,8 +29,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
lintOptions {
disable 'MissingTranslation'
+ disable 'ExtraTranslation'
}
}
@@ -36,28 +42,36 @@ repositories {
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.android.support:appcompat-v7:23.1.1'
- compile 'com.android.support:recyclerview-v7:23.1.1'
- compile 'com.android.support:cardview-v7:23.1.1'
- compile 'com.android.support:palette-v7:23.1.1'
- compile 'com.android.support:design:23.1.1'
- compile 'com.android.support:percent:23.1.1'
-
- compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'
- compile 'net.steamcrafted:materialiconlib:1.0.3'
- compile 'com.squareup.retrofit:retrofit:1.9.0'
- compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
- compile 'com.squareup.okhttp:okhttp:2.3.0'
- compile 'com.google.code.gson:gson:2.3'
-
- compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') {
- transitive = true
- }
- compile('com.github.naman14:app-theme-engine:-SNAPSHOT@aar') {
- transitive = true
- }
- compile('com.github.afollestad.material-dialogs:commons:0.8.5.3@aar') {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation "androidx.appcompat:appcompat:1.0.2"
+ implementation "com.google.android.material:material:1.0.0"
+ implementation "androidx.cardview:cardview:1.0.0"
+ implementation "androidx.recyclerview:recyclerview:1.1.0"
+ implementation "androidx.palette:palette:1.0.0"
+ implementation "androidx.percentlayout:percentlayout:1.0.0"
+ implementation 'androidx.multidex:multidex:2.0.1'
+
+ implementation "androidx.mediarouter:mediarouter:1.1.0"
+ implementation 'com.google.android.gms:play-services-cast-framework:16.1.2'
+
+ implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.4'
+ implementation 'net.steamcrafted:materialiconlib:1.1.4'
+ implementation 'com.squareup.retrofit:retrofit:1.9.0'
+ implementation 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
+ implementation 'com.squareup.okhttp:okhttp:2.3.0'
+ implementation 'com.google.code.gson:gson:2.3'
+ implementation 'de.Maxr1998:track-selector-lib:1.2'
+
+ implementation 'com.afollestad.material-dialogs:core:0.9.0.2'
+ implementation 'com.afollestad.material-dialogs:commons:0.9.0.2'
+
+ implementation('com.github.naman14:app-theme-engine:0.5.2@aar') {
transitive = true
}
+ implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
+
+ implementation 'com.anjlab.android.iab.v3:library:1.0.+'
+ implementation 'org.nanohttpd:nanohttpd:2.3.1'
}
+
+apply from: '../mock.gradle'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0300c5e26..d21889fff 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,29 +1,71 @@
+
+
+
+
+ android:label="Timber"
+ android:theme="@style/AppTheme.FullScreen.Light"
+ tools:replace="android:allowBackup, android:label">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -54,6 +106,50 @@
android:label="@string/app_name"
android:process=":main" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/aidl/com/naman14/timber/ITimberService.aidl b/app/src/main/aidl/com/naman14/timber/ITimberService.aidl
index d545adcbe..10236c828 100644
--- a/app/src/main/aidl/com/naman14/timber/ITimberService.aidl
+++ b/app/src/main/aidl/com/naman14/timber/ITimberService.aidl
@@ -48,6 +48,5 @@ interface ITimberService
int getRepeatMode();
int getMediaMountedCount();
int getAudioSessionId();
- void setLockscreenAlbumArt(boolean enabled);
}
diff --git a/app/src/main/assets/materialdesignicons-webfont.ttf b/app/src/main/assets/materialdesignicons-webfont.ttf
index 8bb426d3b..69404e3d9 100644
Binary files a/app/src/main/assets/materialdesignicons-webfont.ttf and b/app/src/main/assets/materialdesignicons-webfont.ttf differ
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
deleted file mode 100644
index 8c3b0ffe8..000000000
Binary files a/app/src/main/ic_launcher-web.png and /dev/null differ
diff --git a/app/src/main/java/com/naman14/timber/MusicPlayer.java b/app/src/main/java/com/naman14/timber/MusicPlayer.java
index aaeb55b41..feb0ce270 100644
--- a/app/src/main/java/com/naman14/timber/MusicPlayer.java
+++ b/app/src/main/java/com/naman14/timber/MusicPlayer.java
@@ -99,16 +99,7 @@ public static void next() {
}
public static void initPlaybackServiceWithSettings(final Context context) {
- setShowAlbumArtOnLockscreen(true);
- }
- public static void setShowAlbumArtOnLockscreen(final boolean enabled) {
- try {
- if (mService != null) {
- mService.setLockscreenAlbumArt(enabled);
- }
- } catch (final RemoteException ignored) {
- }
}
public static void asyncNext(final Context context) {
@@ -388,6 +379,15 @@ public static void setQueuePosition(final int position) {
}
}
+ public static void refresh() {
+ try {
+ if (mService != null) {
+ mService.refresh();
+ }
+ } catch (final RemoteException ignored) {
+ }
+ }
+
public static final int getQueueHistorySize() {
if (mService != null) {
try {
@@ -506,27 +506,19 @@ public static void playNext(Context context, final long[] list, final long sourc
public static void shuffleAll(final Context context) {
Cursor cursor = SongLoader.makeSongCursor(context, null, null);
- final long[] mTrackList = SongLoader.getSongListForCursor(cursor);
- final int position = 0;
- if (mTrackList.length == 0 || mService == null) {
+ final long[] trackList = SongLoader.getSongListForCursor(cursor);
+ if (trackList.length == 0 || mService == null) {
return;
}
try {
mService.setShuffleMode(MusicService.SHUFFLE_NORMAL);
- final long mCurrentId = mService.getAudioId();
- final int mCurrentQueuePosition = getQueuePosition();
- if (position != -1 && mCurrentQueuePosition == position
- && mCurrentId == mTrackList[position]) {
- final long[] mPlaylist = getQueue();
- if (Arrays.equals(mTrackList, mPlaylist)) {
+ if (getQueuePosition() == 0 && mService.getAudioId() == trackList[0] && Arrays.equals(trackList, getQueue())) {
mService.play();
return;
- }
}
- mService.open(mTrackList, -1, -1, IdType.NA.mId);
+ mService.open(trackList, -1, -1, IdType.NA.mId);
mService.play();
cursor.close();
- cursor = null;
} catch (final RemoteException ignored) {
}
}
@@ -615,6 +607,8 @@ public static void seek(final long position) {
try {
mService.seek(position);
} catch (final RemoteException ignored) {
+ } catch (IllegalStateException ignored) {
+
}
}
}
@@ -655,9 +649,11 @@ public static final long duration() {
}
public static void clearQueue() {
- try {
- mService.removeTracks(0, Integer.MAX_VALUE);
- } catch (final RemoteException ignored) {
+ if (mService!=null) {
+ try {
+ mService.removeTracks(0, Integer.MAX_VALUE);
+ } catch (final RemoteException ignored) {
+ }
}
}
@@ -754,6 +750,15 @@ public static final long createPlaylist(final Context context, final String name
return -1;
}
+ public static final void openFile(final String path) {
+ if (mService != null) {
+ try {
+ mService.openFile(path);
+ } catch (final RemoteException ignored) {
+ }
+ }
+ }
+
public static final class ServiceBinder implements ServiceConnection {
private final ServiceConnection mCallback;
private final Context mContext;
diff --git a/app/src/main/java/com/naman14/timber/MusicService.java b/app/src/main/java/com/naman14/timber/MusicService.java
index 69b18941d..398c9670f 100644
--- a/app/src/main/java/com/naman14/timber/MusicService.java
+++ b/app/src/main/java/com/naman14/timber/MusicService.java
@@ -17,8 +17,10 @@
import android.Manifest;
import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
@@ -36,13 +38,14 @@
import android.graphics.Color;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.media.MediaMetadata;
+import android.media.MediaMetadataEditor;
+import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
+import android.media.RemoteControlClient;
import android.media.audiofx.AudioEffect;
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -55,17 +58,27 @@
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AlbumColumns;
import android.provider.MediaStore.Audio.AudioColumns;
-import android.support.v7.graphics.Palette;
+import androidx.core.app.NotificationManagerCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+
+import androidx.media.app.NotificationCompat;
+import androidx.palette.graphics.Palette;
import android.text.TextUtils;
import android.util.Log;
import com.naman14.timber.helpers.MediaButtonIntentReceiver;
import com.naman14.timber.helpers.MusicPlaybackTrack;
+import com.naman14.timber.lastfmapi.LastFmClient;
+import com.naman14.timber.lastfmapi.models.LastfmUserSession;
+import com.naman14.timber.lastfmapi.models.ScrobbleQuery;
import com.naman14.timber.permissions.Nammu;
import com.naman14.timber.provider.MusicPlaybackState;
import com.naman14.timber.provider.RecentStore;
import com.naman14.timber.provider.SongPlayCount;
import com.naman14.timber.utils.NavigationUtils;
+import com.naman14.timber.utils.PreferencesUtility;
import com.naman14.timber.utils.TimberUtils;
import com.naman14.timber.utils.TimberUtils.IdType;
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -78,6 +91,10 @@
import java.util.Random;
import java.util.TreeSet;
+import de.Maxr1998.trackselectorlib.ModNotInstalledException;
+import de.Maxr1998.trackselectorlib.NotificationHelper;
+import de.Maxr1998.trackselectorlib.TrackItem;
+
@SuppressLint("NewApi")
public class MusicService extends Service {
public static final String PLAYSTATE_CHANGED = "com.naman14.timber.playstatechanged";
@@ -110,6 +127,8 @@ public class MusicService extends Service {
public static final String CMDPREVIOUS = "previous";
public static final String CMDNEXT = "next";
public static final String CMDNOTIF = "buttonId";
+ public static final String UPDATE_PREFERENCES = "updatepreferences";
+ public static final String CHANNEL_ID = "timber_channel_01";
public static final int NEXT = 2;
public static final int LAST = 3;
public static final int SHUFFLE_NONE = 0;
@@ -142,6 +161,10 @@ public class MusicService extends Service {
MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ARTIST,
MediaStore.Audio.Albums.LAST_YEAR
};
+ private static final String[] NOTIFICATION_PROJECTION = new String[]{
+ "audio._id AS _id", AudioColumns.ALBUM_ID, AudioColumns.TITLE,
+ AudioColumns.ARTIST, AudioColumns.DURATION
+ };
private static final Shuffler mShuffler = new Shuffler();
private static final int NOTIFY_MODE_NONE = 0;
private static final int NOTIFY_MODE_FOREGROUND = 1;
@@ -160,7 +183,7 @@ public class MusicService extends Service {
private AlarmManager mAlarmManager;
private PendingIntent mShutdownIntent;
private boolean mShutdownScheduled;
- private NotificationManager mNotificationManager;
+ private NotificationManagerCompat mNotificationManager;
private Cursor mCursor;
private Cursor mAlbumCursor;
private AudioManager mAudioManager;
@@ -173,7 +196,9 @@ public class MusicService extends Service {
private boolean mQueueIsSaveable = true;
private boolean mPausedByTransientLossOfFocus = false;
- private MediaSession mSession;
+ private MediaSessionCompat mSession;
+ @SuppressWarnings("deprecation")
+ private RemoteControlClient mRemoteControlClient;
private ComponentName mMediaButtonReceiverComponent;
@@ -209,6 +234,7 @@ public void onAudioFocusChange(final int focusChange) {
private BroadcastReceiver mUnmountReceiver = null;
private MusicPlaybackState mPlaybackStateStore;
private boolean mShowAlbumArtOnLockscreen;
+ private boolean mActivateXTrackSelector;
private SongPlayCount mSongPlayCount;
private RecentStore mRecentStore;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -262,7 +288,8 @@ public void onCreate() {
if (D) Log.d(TAG, "Creating service");
super.onCreate();
- mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ mNotificationManager = NotificationManagerCompat.from(this);
+ createNotificationChannel();
// gets a pointer to the playback state store
mPlaybackStateStore = MusicPlaybackState.getInstance(this);
@@ -285,6 +312,8 @@ public void onCreate() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
setUpMediaSession();
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ setUpRemoteControlClient();
mPreferences = getSharedPreferences("Service", 0);
mCardId = getCardId();
@@ -305,6 +334,8 @@ public void onCreate() {
filter.addAction(PREVIOUS_FORCE_ACTION);
filter.addAction(REPEAT_ACTION);
filter.addAction(SHUFFLE_ACTION);
+ filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
// Attach the broadcast listener
registerReceiver(mIntentReceiver, filter);
@@ -331,11 +362,40 @@ public void onCreate() {
reloadQueueAfterPermissionCheck();
notifyChange(QUEUE_CHANGED);
notifyChange(META_CHANGED);
+ //Try to push LastFMCache
+ if (LastfmUserSession.getSession(this) != null) {
+ LastFmClient.getInstance(this).Scrobble(null);
+ }
+ PreferencesUtility pref = PreferencesUtility.getInstance(this);
+ mShowAlbumArtOnLockscreen = pref.getSetAlbumartLockscreen();
+ mActivateXTrackSelector = pref.getXPosedTrackselectorEnabled();
+ }
+
+ @SuppressWarnings("deprecation")
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void setUpRemoteControlClient() {
+ //Legacy for ICS
+ if (mRemoteControlClient == null) {
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.setComponent(mMediaButtonReceiverComponent);
+ PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
+
+ // create and register the remote control client
+ mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
+ mAudioManager.registerRemoteControlClient(mRemoteControlClient);
+ }
+
+ mRemoteControlClient.setTransportControlFlags(
+ RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
+ RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
+ RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
+ RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
+ RemoteControlClient.FLAG_KEY_MEDIA_STOP);
}
private void setUpMediaSession() {
- mSession = new MediaSession(this, "Timber");
- mSession.setCallback(new MediaSession.Callback() {
+ mSession = new MediaSessionCompat(this, "Timber");
+ mSession.setCallback(new MediaSessionCompat.Callback() {
@Override
public void onPause() {
pause();
@@ -370,13 +430,18 @@ public void onStop() {
releaseServiceUiAndStop();
}
});
- mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
+ | MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
}
@Override
public void onDestroy() {
if (D) Log.d(TAG, "Destroying service");
super.onDestroy();
+ //Try to push LastFMCache
+ if (LastfmUserSession.getSession(this).isLogedin()) {
+ LastFmClient.getInstance(this).Scrobble(null);
+ }
// Remove any sound effects
final Intent audioEffectsIntent = new Intent(
AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
@@ -436,7 +501,16 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
MediaButtonIntentReceiver.completeWakefulIntent(intent);
}
- return START_STICKY;
+ return START_NOT_STICKY; //no sense to use START_STICKY with using startForeground
+ }
+
+ void scrobble() {
+ if (LastfmUserSession.getSession(this).isLogedin()) {
+ Log.d("Scrobble", "to LastFM");
+ String trackname = getTrackName();
+ if (trackname != null)
+ LastFmClient.getInstance(this).Scrobble(new ScrobbleQuery(getArtistName(), trackname, System.currentTimeMillis() / 1000));
+ }
}
private void releaseServiceUiAndStop() {
@@ -464,6 +538,11 @@ private void handleCommandIntent(Intent intent) {
if (D) Log.d(TAG, "handleCommandIntent: action = " + action + ", command = " + command);
+ if (NotificationHelper.checkIntent(intent)) {
+ goToPosition(mPlayPos + NotificationHelper.getPosition(intent));
+ return;
+ }
+
if (CMDNEXT.equals(command) || NEXT_ACTION.equals(action)) {
gotoNext(true);
} else if (CMDPREVIOUS.equals(command) || PREVIOUS_ACTION.equals(action)
@@ -490,9 +569,30 @@ private void handleCommandIntent(Intent intent) {
cycleRepeat();
} else if (SHUFFLE_ACTION.equals(action)) {
cycleShuffle();
+ } else if (UPDATE_PREFERENCES.equals(action)) {
+ onPreferencesUpdate(intent.getExtras());
+ }
+ else if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(action)) {
+ if (PreferencesUtility.getInstance(getApplicationContext()).pauseEnabledOnDetach()) {
+ pause();
+ }
}
}
+ private void onPreferencesUpdate(Bundle extras) {
+ mShowAlbumArtOnLockscreen = extras.getBoolean("lockscreen", mShowAlbumArtOnLockscreen);
+ mActivateXTrackSelector = extras.getBoolean("xtrack",mActivateXTrackSelector);
+ LastfmUserSession session = LastfmUserSession.getSession(this);
+ session.mToken = extras.getString("lf_token", session.mToken);
+ session.mUsername = extras.getString("lf_user", session.mUsername);
+ if ("logout".equals(session.mToken)) {
+ session.mToken = null;
+ session.mUsername = null;
+ }
+ notifyChange(META_CHANGED);
+
+ }
+
private void updateNotification() {
final int newNotifyMode;
if (isPlaying()) {
@@ -608,6 +708,12 @@ private void cancelShutdown() {
private void stop(final boolean goToIdle) {
if (D) Log.d(TAG, "Stopping playback, goToIdle = " + goToIdle);
+ long duration = this.duration();
+ long position = this.position();
+ if (duration > 30000 && (position >= duration / 2) || position > 240000) {
+ scrobble();
+ }
+
if (mPlayer.isInitialized()) {
mPlayer.stop();
}
@@ -1002,6 +1108,8 @@ private void notifyChange(final String what) {
// Update the lockscreen controls
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
updateMediaSession(what);
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ updateRemoteControlClient(what);
if (what.equals(POSITION_CHANGED)) {
return;
@@ -1011,6 +1119,7 @@ private void notifyChange(final String what) {
intent.putExtra("id", getAudioId());
intent.putExtra("artist", getArtistName());
intent.putExtra("album", getAlbumName());
+ intent.putExtra("albumid", getAlbumId());
intent.putExtra("track", getTrackName());
intent.putExtra("playing", isPlaying());
@@ -1046,46 +1155,100 @@ && getShuffleMode() != SHUFFLE_NONE) {
}
+ @SuppressWarnings("deprecation")
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void updateRemoteControlClient(final String what) {
+ //Legacy for ICS
+ if (mRemoteControlClient != null) {
+ int playState = mIsSupposedToBePlaying
+ ? RemoteControlClient.PLAYSTATE_PLAYING
+ : RemoteControlClient.PLAYSTATE_PAUSED;
+ if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
+ Bitmap albumArt = null;
+ if (mShowAlbumArtOnLockscreen) {
+ albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());
+ if (albumArt != null) {
+
+ Bitmap.Config config = albumArt.getConfig();
+ if (config == null) {
+ config = Bitmap.Config.ARGB_8888;
+ }
+ albumArt = albumArt.copy(config, false);
+ }
+ }
+
+ RemoteControlClient.MetadataEditor editor = mRemoteControlClient.editMetadata(true);
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getAlbumName());
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getArtistName());
+ editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getTrackName());
+ editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration());
+ editor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, albumArt);
+ editor.apply();
+
+ }
+ mRemoteControlClient.setPlaybackState(playState);
+ }
+ }
+
+
private void updateMediaSession(final String what) {
int playState = mIsSupposedToBePlaying
- ? PlaybackState.STATE_PLAYING
- : PlaybackState.STATE_PAUSED;
+ ? PlaybackStateCompat.STATE_PLAYING
+ : PlaybackStateCompat.STATE_PAUSED;
if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_CHANGED)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- mSession.setPlaybackState(new PlaybackState.Builder()
- .setState(playState, position(), 1.0f).build());
+ mSession.setPlaybackState(new PlaybackStateCompat.Builder()
+ .setState(playState, position(), 1.0f)
+ .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE |
+ PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
+ .build());
}
} else if (what.equals(META_CHANGED) || what.equals(QUEUE_CHANGED)) {
- Bitmap albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());
- if (albumArt != null) {
-
- Bitmap.Config config = albumArt.getConfig();
- if (config == null) {
- config = Bitmap.Config.ARGB_8888;
+ Bitmap albumArt = null;
+ if (mShowAlbumArtOnLockscreen) {
+ albumArt = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(getAlbumId()).toString());
+ if (albumArt != null) {
+
+ Bitmap.Config config = albumArt.getConfig();
+ if (config == null) {
+ config = Bitmap.Config.ARGB_8888;
+ }
+ albumArt = albumArt.copy(config, false);
}
- albumArt = albumArt.copy(config, false);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- mSession.setMetadata(new MediaMetadata.Builder()
- .putString(MediaMetadata.METADATA_KEY_ARTIST, getArtistName())
- .putString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName())
- .putString(MediaMetadata.METADATA_KEY_ALBUM, getAlbumName())
- .putString(MediaMetadata.METADATA_KEY_TITLE, getTrackName())
- .putLong(MediaMetadata.METADATA_KEY_DURATION, duration())
- .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1)
- .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, getQueue().length)
- .putString(MediaMetadata.METADATA_KEY_GENRE, getGenreName())
- .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART,
- mShowAlbumArtOnLockscreen ? albumArt : null)
+ mSession.setMetadata(new MediaMetadataCompat.Builder()
+ .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, getArtistName())
+ .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, getAlbumArtistName())
+ .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, getAlbumName())
+ .putString(MediaMetadataCompat.METADATA_KEY_TITLE, getTrackName())
+ .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration())
+ .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getQueuePosition() + 1)
+ .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getQueue().length)
+ .putString(MediaMetadataCompat.METADATA_KEY_GENRE, getGenreName())
+ .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt)
.build());
- mSession.setPlaybackState(new PlaybackState.Builder()
- .setState(playState, position(), 1.0f).build());
+ mSession.setPlaybackState(new PlaybackStateCompat.Builder()
+ .setState(playState, position(), 1.0f)
+ .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_PLAY_PAUSE |
+ PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
+ .build());
}
}
}
+ private void createNotificationChannel() {
+ if (TimberUtils.isOreo()) {
+ CharSequence name = "Timber";
+ int importance = NotificationManager.IMPORTANCE_LOW;
+ NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance);
+ manager.createNotificationChannel(mChannel);
+ }
+ }
+
private Notification buildNotification() {
final String albumName = getAlbumName();
final String artistName = getArtistName();
@@ -1095,9 +1258,6 @@ private Notification buildNotification() {
int playButtonResId = isPlaying
? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp;
- int playButtonTitleResId = isPlaying
- ? R.string.accessibility_pause : R.string.accessibility_play;
-
Intent nowPlayingIntent = NavigationUtils.getNowPlayingIntent(this);
PendingIntent clickIntent = PendingIntent.getActivity(this, 0, nowPlayingIntent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -1112,7 +1272,7 @@ private Notification buildNotification() {
mNotificationPostTime = System.currentTimeMillis();
}
- Notification.Builder builder = new Notification.Builder(this)
+ androidx.core.app.NotificationCompat.Builder builder = new androidx.core.app.NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(artwork)
.setContentIntent(clickIntent)
@@ -1131,17 +1291,60 @@ private Notification buildNotification() {
if (TimberUtils.isJellyBeanMR1()) {
builder.setShowWhen(false);
}
+
if (TimberUtils.isLollipop()) {
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
- Notification.MediaStyle style = new Notification.MediaStyle()
+ NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle()
.setMediaSession(mSession.getSessionToken())
.setShowActionsInCompactView(0, 1, 2, 3);
builder.setStyle(style);
}
- if (artwork != null && TimberUtils.isLollipop())
+ if (artwork != null && TimberUtils.isLollipop()) {
builder.setColor(Palette.from(artwork).generate().getVibrantColor(Color.parseColor("#403f4d")));
+ }
+
+ if (TimberUtils.isOreo()) {
+ builder.setColorized(true);
+ }
+
+ Notification n = builder.build();
- return builder.build();
+ if (mActivateXTrackSelector) {
+ addXTrackSelector(n);
+ }
+
+ return n;
+ }
+
+ private void addXTrackSelector(Notification n) {
+ if (NotificationHelper.isSupported(n)) {
+ StringBuilder selection = new StringBuilder();
+ StringBuilder order = new StringBuilder().append("CASE _id \n");
+ for (int i = 0; i < mPlaylist.size(); i++) {
+ selection.append("_id=").append(mPlaylist.get(i).mId).append(" OR ");
+ order.append("WHEN ").append(mPlaylist.get(i).mId).append(" THEN ").append(i).append("\n");
+ }
+ order.append("END");
+ Cursor c = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, NOTIFICATION_PROJECTION, selection.substring(0, selection.length() - 3), null, order.toString());
+ if (c != null && c.getCount() != 0) {
+ c.moveToFirst();
+ ArrayList list = new ArrayList<>();
+ do {
+ TrackItem t = new TrackItem()
+ .setArt(TimberUtils.getAlbumArtUri(c.getLong(c.getColumnIndexOrThrow(AudioColumns.ALBUM_ID))))
+ .setTitle(c.getString(c.getColumnIndexOrThrow(AudioColumns.TITLE)))
+ .setArtist(c.getString(c.getColumnIndexOrThrow(AudioColumns.ARTIST)))
+ .setDuration(TimberUtils.makeShortTimeString(this, c.getInt(c.getColumnIndexOrThrow(AudioColumns.DURATION)) / 1000));
+ list.add(t.get());
+ } while (c.moveToNext());
+ try {
+ NotificationHelper.insertToNotification(n, list, this, getQueuePosition());
+ } catch (ModNotInstalledException e) {
+ e.printStackTrace();
+ }
+ c.close();
+ }
+ }
}
private final PendingIntent retrievePlaybackAction(final String action) {
@@ -1858,6 +2061,30 @@ public void gotoNext(final boolean force) {
}
}
+ public void goToPosition(int pos) {
+ synchronized (this) {
+ if (mPlaylist.size() <= 0) {
+ if (D) Log.d(TAG, "No play queue");
+ scheduleDelayedShutdown();
+ return;
+ }
+ if (pos < 0) {
+ return;
+ }
+ if (pos == mPlayPos) {
+ if (!isPlaying()) {
+ play();
+ }
+ return;
+ }
+ stop(false);
+ setAndRecordPlayPos(pos);
+ openCurrentAndNext();
+ play();
+ notifyChange(META_CHANGED);
+ }
+ }
+
public void setAndRecordPlayPos(int nextPos) {
synchronized (this) {
@@ -1874,8 +2101,6 @@ public void setAndRecordPlayPos(int nextPos) {
public void prev(boolean forcePrevious) {
synchronized (this) {
-
-
boolean goPrevious = getRepeatMode() != REPEAT_CURRENT &&
(position() < REWIND_INSTEAD_PREVIOUS_THRESHOLD || forcePrevious);
@@ -2010,11 +2235,6 @@ public void playlistChanged() {
notifyChange(PLAYLIST_CHANGED);
}
- public void setLockscreenAlbumArt(boolean enabled) {
- mShowAlbumArtOnLockscreen = enabled;
- notifyChange(META_CHANGED);
- }
-
public interface TrackErrorExtra {
String TRACK_NAME = "trackname";
@@ -2070,6 +2290,7 @@ public void handleMessage(final Message msg) {
}
break;
case TRACK_WENT_TO_NEXT:
+ mService.get().scrobble();
service.setAndRecordPlayPos(service.mNextPlayPos);
service.setNextTrack();
if (service.mCursor != null) {
@@ -2201,9 +2422,13 @@ public MultiPlayer(final MusicService service) {
public void setDataSource(final String path) {
- mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
- if (mIsInitialized) {
- setNextDataSource(null);
+ try {
+ mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
+ if (mIsInitialized) {
+ setNextDataSource(null);
+ }
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
}
}
@@ -2253,14 +2478,18 @@ public void setNextDataSource(final String path) {
mNextMediaPlayer = new MediaPlayer();
mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
- if (setDataSourceImpl(mNextMediaPlayer, path)) {
- mNextMediaPath = path;
- mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
- } else {
- if (mNextMediaPlayer != null) {
- mNextMediaPlayer.release();
- mNextMediaPlayer = null;
+ try {
+ if (setDataSourceImpl(mNextMediaPlayer, path)) {
+ mNextMediaPath = path;
+ mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
+ } else {
+ if (mNextMediaPlayer != null) {
+ mNextMediaPlayer.release();
+ mNextMediaPlayer = null;
+ }
}
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
}
}
@@ -2313,7 +2542,11 @@ public long seek(final long whereto) {
public void setVolume(final float vol) {
- mCurrentMediaPlayer.setVolume(vol, vol);
+ try {
+ mCurrentMediaPlayer.setVolume(vol, vol);
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
}
public int getAudioSessionId() {
@@ -2600,12 +2833,6 @@ public int getAudioSessionId() throws RemoteException {
return mService.get().getAudioSessionId();
}
-
- @Override
- public void setLockscreenAlbumArt(boolean enabled) {
- mService.get().setLockscreenAlbumArt(enabled);
- }
-
}
private class MediaStoreObserver extends ContentObserver implements Runnable {
@@ -2633,5 +2860,4 @@ public void run() {
refresh();
}
}
-
}
diff --git a/app/src/main/java/com/naman14/timber/TimberApp.java b/app/src/main/java/com/naman14/timber/TimberApp.java
index 226f93a84..84012fb5a 100644
--- a/app/src/main/java/com/naman14/timber/TimberApp.java
+++ b/app/src/main/java/com/naman14/timber/TimberApp.java
@@ -14,16 +14,20 @@
package com.naman14.timber;
-import android.app.Application;
+import androidx.multidex.MultiDexApplication;
import com.afollestad.appthemeengine.ATE;
import com.naman14.timber.permissions.Nammu;
+import com.naman14.timber.utils.PreferencesUtility;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
+import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import com.nostra13.universalimageloader.utils.L;
-public class TimberApp extends Application {
+import java.io.IOException;
+import java.io.InputStream;
+public class TimberApp extends MultiDexApplication {
private static TimberApp mInstance;
@@ -35,7 +39,18 @@ public static synchronized TimberApp getInstance() {
public void onCreate() {
super.onCreate();
mInstance = this;
- ImageLoaderConfiguration localImageLoaderConfiguration = new ImageLoaderConfiguration.Builder(this).build();
+
+ ImageLoaderConfiguration localImageLoaderConfiguration = new ImageLoaderConfiguration.Builder(this).imageDownloader(new BaseImageDownloader(this) {
+ PreferencesUtility prefs = PreferencesUtility.getInstance(TimberApp.this);
+
+ @Override
+ protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException {
+ if (prefs.loadArtistAndAlbumImages())
+ return super.getStreamFromNetwork(imageUri, extra);
+ throw new IOException();
+ }
+ }).build();
+
ImageLoader.getInstance().init(localImageLoaderConfiguration);
L.writeLogs(false);
L.disableLogging();
diff --git a/app/src/main/java/com/naman14/timber/WearBrowserService.java b/app/src/main/java/com/naman14/timber/WearBrowserService.java
new file mode 100644
index 000000000..949d703a9
--- /dev/null
+++ b/app/src/main/java/com/naman14/timber/WearBrowserService.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2015 Naman Dwivedi
+ *
+ * Licensed under the GNU General Public License v3
+ *
+ * This is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ */
+
+package com.naman14.timber;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaSession;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.service.media.MediaBrowserService;
+import androidx.annotation.Nullable;
+
+import com.naman14.timber.dataloaders.AlbumLoader;
+import com.naman14.timber.dataloaders.AlbumSongLoader;
+import com.naman14.timber.dataloaders.ArtistAlbumLoader;
+import com.naman14.timber.dataloaders.ArtistLoader;
+import com.naman14.timber.dataloaders.ArtistSongLoader;
+import com.naman14.timber.dataloaders.PlaylistLoader;
+import com.naman14.timber.dataloaders.PlaylistSongLoader;
+import com.naman14.timber.dataloaders.SongLoader;
+import com.naman14.timber.models.Album;
+import com.naman14.timber.models.Artist;
+import com.naman14.timber.models.Playlist;
+import com.naman14.timber.models.Song;
+import com.naman14.timber.utils.TimberUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@TargetApi(21)
+public class WearBrowserService extends MediaBrowserService {
+
+ public static final String MEDIA_ID_ROOT = "__ROOT__";
+ public static final int TYPE_ARTIST = 0;
+ public static final int TYPE_ALBUM = 1;
+ public static final int TYPE_SONG = 2;
+ public static final int TYPE_PLAYLIST = 3;
+ public static final int TYPE_ARTIST_SONG_ALBUMS = 4;
+ public static final int TYPE_ALBUM_SONGS = 5;
+ public static final int TYPE_ARTIST_ALL_SONGS = 6;
+ public static final int TYPE_PLAYLIST_ALL_SONGS = 7;
+
+ MediaSession mSession;
+ public static WearBrowserService sInstance;
+
+ private Context mContext;
+ private boolean mServiceStarted;
+
+ public static WearBrowserService getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ sInstance = this;
+ mContext = this;
+ mSession = new MediaSession(this, "WearBrowserService");
+ setSessionToken(mSession.getSessionToken());
+ mSession.setCallback(new MediaSessionCallback());
+ mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+
+ }
+
+ @Override
+ public int onStartCommand(Intent startIntent, int flags, int startId) {
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ mServiceStarted = false;
+ mSession.release();
+ }
+
+ @Override
+ public void onLoadChildren(String parentId, Result> result) {
+
+ result.detach();
+ loadChildren(parentId, result);
+
+ }
+
+ @Nullable
+ @Override
+ public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ return new BrowserRoot(MEDIA_ID_ROOT, null);
+ }
+
+ private final class MediaSessionCallback extends MediaSession.Callback {
+
+ @Override
+ public void onPlay() {
+ setSessionActive();
+ }
+
+ @Override
+ public void onSeekTo(long position) {
+
+ }
+
+ @Override
+ public void onPlayFromMediaId(final String mediaId, Bundle extras) {
+ long songId = Long.parseLong(mediaId);
+ setSessionActive();
+ MusicPlayer.playAll(mContext, new long[]{songId}, 0, -1, TimberUtils.IdType.NA, false);
+ }
+
+ @Override
+ public void onPause() {
+
+ }
+
+ @Override
+ public void onStop() {
+ setSessionInactive();
+ }
+
+ @Override
+ public void onSkipToNext() {
+
+ }
+
+ @Override
+ public void onSkipToPrevious() {
+
+ }
+
+ @Override
+ public void onFastForward() {
+
+ }
+
+ @Override
+ public void onRewind() {
+
+ }
+
+ @Override
+ public void onCustomAction(String action, Bundle extras) {
+
+ }
+ }
+
+ private void setSessionActive() {
+ if (!mServiceStarted) {
+ startService(new Intent(getApplicationContext(), WearBrowserService.class));
+ mServiceStarted = true;
+ }
+
+ if (!mSession.isActive()) {
+ mSession.setActive(true);
+ }
+ }
+
+ private void setSessionInactive() {
+ if (mServiceStarted) {
+ stopSelf();
+ mServiceStarted = false;
+ }
+
+ if (mSession.isActive()) {
+ mSession.setActive(false);
+ }
+ }
+
+ private void addMediaRoots(List mMediaRoot) {
+ mMediaRoot.add(new MediaBrowser.MediaItem(
+ new MediaDescription.Builder()
+ .setMediaId(Integer.toString(TYPE_ARTIST))
+ .setTitle(getString(R.string.artists))
+ .setIconUri(Uri.parse("android.resource://" +
+ "naman14.timber/drawable/ic_empty_music2"))
+ .setSubtitle(getString(R.string.artists))
+ .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
+ ));
+
+ mMediaRoot.add(new MediaBrowser.MediaItem(
+ new MediaDescription.Builder()
+ .setMediaId(Integer.toString(TYPE_ALBUM))
+ .setTitle(getString(R.string.albums))
+ .setIconUri(Uri.parse("android.resource://" +
+ "naman14.timber/drawable/ic_empty_music2"))
+ .setSubtitle(getString(R.string.albums))
+ .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
+ ));
+
+ mMediaRoot.add(new MediaBrowser.MediaItem(
+ new MediaDescription.Builder()
+ .setMediaId(Integer.toString(TYPE_SONG))
+ .setTitle(getString(R.string.songs))
+ .setIconUri(Uri.parse("android.resource://" +
+ "naman14.timber/drawable/ic_empty_music2"))
+ .setSubtitle(getString(R.string.songs))
+ .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
+ ));
+
+
+ mMediaRoot.add(new MediaBrowser.MediaItem(
+ new MediaDescription.Builder()
+ .setMediaId(Integer.toString(TYPE_PLAYLIST))
+ .setTitle(getString(R.string.playlists))
+ .setIconUri(Uri.parse("android.resource://" +
+ "naman14.timber/drawable/ic_empty_music2"))
+ .setSubtitle(getString(R.string.playlists))
+ .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE
+ ));
+
+ }
+
+
+ private void loadChildren(final String parentId, final Result> result) {
+
+ final List mediaItems = new ArrayList<>();
+
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(final Void... unused) {
+
+ if (parentId.equals(MEDIA_ID_ROOT)) {
+ addMediaRoots(mediaItems);
+ } else {
+ switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) {
+ case TYPE_ARTIST:
+ List artistList = ArtistLoader.getAllArtists(mContext);
+ for (Artist artist : artistList) {
+ String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount);
+ String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount);
+ fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_SONG_ALBUMS) + Long.toString(artist.id), artist.name, Uri.parse("android.resource://" +
+ "naman14.timber/drawable/ic_empty_music2"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount), MediaBrowser.MediaItem.FLAG_BROWSABLE);
+ }
+ break;
+ case TYPE_ALBUM:
+ List albumList = AlbumLoader.getAllAlbums(mContext);
+ for (Album album : albumList) {
+ fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), album.artistName, MediaBrowser.MediaItem.FLAG_BROWSABLE);
+ }
+ break;
+ case TYPE_SONG:
+ List songList = SongLoader.getAllSongs(mContext);
+ for (Song song : songList) {
+ fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
+ }
+ break;
+ case TYPE_ALBUM_SONGS:
+ List albumSongList = AlbumSongLoader.getSongsForAlbum(mContext, Long.parseLong(parentId.substring(1)));
+ for (Song song : albumSongList) {
+ fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
+ }
+ break;
+ case TYPE_ARTIST_SONG_ALBUMS:
+ fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_ALL_SONGS) + Long.parseLong(parentId.substring(1)), "All songs", Uri.parse("android.resource://" +
+ "naman14.timber/drawable/ic_empty_music2"), "All songs by artist", MediaBrowser.MediaItem.FLAG_BROWSABLE);
+ List artistAlbums = ArtistAlbumLoader.getAlbumsForArtist(mContext, Long.parseLong(parentId.substring(1)));
+ for (Album album : artistAlbums) {
+ String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, album.songCount);
+ fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE);
+
+ }
+ break;
+ case TYPE_ARTIST_ALL_SONGS:
+ List artistSongs = ArtistSongLoader.getSongsForArtist(mContext, Long.parseLong(parentId.substring(1)));
+ for (Song song : artistSongs) {
+ fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
+ }
+ break;
+ case TYPE_PLAYLIST:
+ List playlistList = PlaylistLoader.getPlaylists(mContext, false);
+ for (Playlist playlist : playlistList) {
+ String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, playlist.songCount);
+ fillMediaItems(mediaItems, Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id), playlist.name,
+ Uri.parse("android.resource://" +
+ "naman14.timber/drawable/ic_empty_music2"), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE);
+ }
+ break;
+ case TYPE_PLAYLIST_ALL_SONGS:
+ List playlistSongs = PlaylistSongLoader.getSongsInPlaylist(mContext, Long.parseLong(parentId.substring(1)));
+ for (Song song : playlistSongs) {
+ fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE);
+ }
+ break;
+
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ result.sendResult(mediaItems);
+ }
+ }.execute();
+
+ }
+
+ private void fillMediaItems(List mediaItems, String mediaId, String title, Uri icon, String subTitle, int playableOrBrowsable) {
+ mediaItems.add(new MediaBrowser.MediaItem(
+ new MediaDescription.Builder()
+ .setMediaId(mediaId)
+ .setTitle(title)
+ .setIconUri(icon)
+ .setSubtitle(subTitle)
+ .build(), playableOrBrowsable
+ ));
+ }
+
+}
diff --git a/app/src/main/java/com/naman14/timber/activities/BaseActivity.java b/app/src/main/java/com/naman14/timber/activities/BaseActivity.java
index 0953cc3ad..5a1bf8960 100644
--- a/app/src/main/java/com/naman14/timber/activities/BaseActivity.java
+++ b/app/src/main/java/com/naman14/timber/activities/BaseActivity.java
@@ -21,12 +21,13 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentManager;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -34,10 +35,20 @@
import com.afollestad.appthemeengine.ATE;
import com.afollestad.appthemeengine.ATEActivity;
+import com.google.android.gms.cast.framework.CastButtonFactory;
+import com.google.android.gms.cast.framework.CastContext;
+import com.google.android.gms.cast.framework.CastSession;
+import com.google.android.gms.cast.framework.Session;
+import com.google.android.gms.cast.framework.SessionManager;
+import com.google.android.gms.cast.framework.SessionManagerListener;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
import com.naman14.timber.ITimberService;
import com.naman14.timber.MusicPlayer;
import com.naman14.timber.MusicService;
import com.naman14.timber.R;
+import com.naman14.timber.cast.SimpleSessionManagerListener;
+import com.naman14.timber.cast.WebServer;
import com.naman14.timber.listeners.MusicStateListener;
import com.naman14.timber.slidinguppanel.SlidingUpPanelLayout;
import com.naman14.timber.subfragments.QuickControlsFragment;
@@ -45,6 +56,7 @@
import com.naman14.timber.utils.NavigationUtils;
import com.naman14.timber.utils.TimberUtils;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -56,6 +68,52 @@ public class BaseActivity extends ATEActivity implements ServiceConnection, Musi
private MusicPlayer.ServiceToken mToken;
private PlaybackStatus mPlaybackStatus;
+ private CastSession mCastSession;
+ private SessionManager mSessionManager;
+ private final SessionManagerListener mSessionManagerListener =
+ new SessionManagerListenerImpl();
+ private WebServer castServer;
+
+ public boolean playServicesAvailable = false;
+
+ private class SessionManagerListenerImpl extends SimpleSessionManagerListener {
+ @Override
+ public void onSessionStarting(Session session) {
+ super.onSessionStarting(session);
+ startCastServer();
+ }
+
+ @Override
+ public void onSessionStarted(Session session, String sessionId) {
+ invalidateOptionsMenu();
+ mCastSession = mSessionManager.getCurrentCastSession();
+ showCastMiniController();
+ }
+ @Override
+ public void onSessionResumed(Session session, boolean wasSuspended) {
+ invalidateOptionsMenu();
+ mCastSession = mSessionManager.getCurrentCastSession();
+ }
+ @Override
+ public void onSessionEnded(Session session, int error) {
+ mCastSession = null;
+ hideCastMiniController();
+ stopCastServer();
+ }
+
+ @Override
+ public void onSessionResuming(Session session, String s) {
+ super.onSessionResuming(session, s);
+ startCastServer();
+ }
+
+ @Override
+ public void onSessionSuspended(Session session, int i) {
+ super.onSessionSuspended(session, i);
+ stopCastServer();
+ }
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -63,7 +121,18 @@ public void onCreate(Bundle savedInstanceState) {
mToken = MusicPlayer.bindToService(this, this);
mPlaybackStatus = new PlaybackStatus(this);
+ //make volume keys change multimedia volume even if music is not playing now
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ try {
+ playServicesAvailable = GoogleApiAvailability
+ .getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS;
+ } catch (Exception ignored) {
+ }
+
+ if (playServicesAvailable)
+ initCast();
}
@Override
@@ -89,24 +158,43 @@ protected void onStart() {
@Override
protected void onStop() {
super.onStop();
-
-
}
@Override
public void onResume() {
- super.onResume();
+ if (playServicesAvailable) {
+ mCastSession = mSessionManager.getCurrentCastSession();
+ mSessionManager.addSessionManagerListener(mSessionManagerListener);
+ }
+ //For Android 8.0+: service may get destroyed if in background too long
+ if(mService == null){
+ mToken = MusicPlayer.bindToService(this, this);
+ }
onMetaChanged();
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (playServicesAvailable) {
+ mSessionManager.removeSessionManagerListener(mSessionManagerListener);
+ mCastSession = null;
+ }
}
@Override
public void onServiceConnected(final ComponentName name, final IBinder service) {
mService = ITimberService.Stub.asInterface(service);
-
onMetaChanged();
}
+ private void initCast() {
+ CastContext castContext = CastContext.getSharedInstance(this);
+ mSessionManager = castContext.getSessionManager();
+ }
+
@Override
public void onServiceDisconnected(final ComponentName name) {
mService = null;
@@ -126,7 +214,6 @@ protected void onDestroy() {
} catch (final Throwable e) {
}
mMusicStateListener.clear();
-
}
@Override
@@ -178,6 +265,15 @@ public void removeMusicStateListenerListener(final MusicStateListener status) {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
+
+ getMenuInflater().inflate(R.menu.menu_cast, menu);
+
+ if (playServicesAvailable) {
+ CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
+ menu,
+ R.id.media_route_menu_item);
+ }
+
if (!TimberUtils.hasEffectsPanel(BaseActivity.this)) {
menu.removeItem(R.id.action_equalizer);
}
@@ -227,19 +323,22 @@ public void setPanelSlideListeners(SlidingUpPanelLayout panelLayout) {
@Override
public void onPanelSlide(View panel, float slideOffset) {
View nowPlayingCard = QuickControlsFragment.topContainer;
- nowPlayingCard.setAlpha(1 - slideOffset);
+ if (nowPlayingCard != null)
+ nowPlayingCard.setAlpha(1 - slideOffset);
}
@Override
public void onPanelCollapsed(View panel) {
View nowPlayingCard = QuickControlsFragment.topContainer;
- nowPlayingCard.setAlpha(1);
+ if (nowPlayingCard != null)
+ nowPlayingCard.setAlpha(1);
}
@Override
public void onPanelExpanded(View panel) {
View nowPlayingCard = QuickControlsFragment.topContainer;
- nowPlayingCard.setAlpha(0);
+ if (nowPlayingCard != null)
+ nowPlayingCard.setAlpha(0);
}
@Override
@@ -298,12 +397,6 @@ protected String doInBackground(String... params) {
@Override
protected void onPostExecute(String result) {
- QuickControlsFragment.topContainer.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- NavigationUtils.navigateToNowplaying(BaseActivity.this, false);
- }
- });
}
@Override
@@ -311,4 +404,30 @@ protected void onPreExecute() {
}
}
+ public void showCastMiniController() {
+ //implement by overriding in activities
+ }
+
+ public void hideCastMiniController() {
+ //implement by overriding in activities
+ }
+
+ public CastSession getCastSession() {
+ return mCastSession;
+ }
+
+ private void startCastServer() {
+ castServer = new WebServer(this);
+ try {
+ castServer.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void stopCastServer() {
+ if (castServer != null) {
+ castServer.stop();
+ }
+ }
}
diff --git a/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java b/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java
index 7ab499149..9454bd11f 100644
--- a/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java
+++ b/app/src/main/java/com/naman14/timber/activities/BaseThemedActivity.java
@@ -1,6 +1,8 @@
package com.naman14.timber.activities;
-import android.support.annotation.Nullable;
+import android.media.AudioManager;
+import android.os.Bundle;
+import androidx.annotation.Nullable;
import com.afollestad.appthemeengine.ATEActivity;
import com.naman14.timber.utils.Helpers;
@@ -15,4 +17,11 @@ public class BaseThemedActivity extends ATEActivity {
public String getATEKey() {
return Helpers.getATEKey(this);
}
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ //make volume keys change multimedia volume even if music is not playing now
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ }
}
diff --git a/app/src/main/java/com/naman14/timber/activities/DonateActivity.java b/app/src/main/java/com/naman14/timber/activities/DonateActivity.java
new file mode 100644
index 000000000..50432e2ae
--- /dev/null
+++ b/app/src/main/java/com/naman14/timber/activities/DonateActivity.java
@@ -0,0 +1,222 @@
+package com.naman14.timber.activities;
+
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import androidx.appcompat.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.anjlab.android.iab.v3.BillingProcessor;
+import com.anjlab.android.iab.v3.SkuDetails;
+import com.anjlab.android.iab.v3.TransactionDetails;
+import com.naman14.timber.R;
+import com.naman14.timber.utils.PreferencesUtility;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Created by naman on 29/10/16.
+ */
+public class DonateActivity extends BaseThemedActivity implements BillingProcessor.IBillingHandler {
+
+ private static final String DONATION_1 = "naman14.timber.donate_1";
+ private static final String DONATION_2 = "naman14.timber.donate_2";
+ private static final String DONATION_3 = "naman14.timber.donate_3";
+ private static final String DONATION_5 = "naman14.timber.donate_5";
+ private static final String DONATION_10 = "naman14.timber.donate_10";
+ private static final String DONATION_20 = "naman14.timber.donate_20";
+
+
+ private boolean readyToPurchase = false;
+ BillingProcessor bp;
+
+ private LinearLayout productListView;
+ private ProgressBar progressBar;
+ private TextView status;
+
+ private String action = "support";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_donate);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle("Support development");
+ action = getIntent().getAction();
+
+ productListView = (LinearLayout) findViewById(R.id.product_list);
+ progressBar = (ProgressBar) findViewById(R.id.progressBar);
+ status = (TextView) findViewById(R.id.donation_status);
+
+ if (action != null && action.equals("restore")) {
+ status.setText("Restoring purchases..");
+ }
+
+ bp = new BillingProcessor(this, getString(R.string.play_billing_license_key), this);
+
+ }
+
+ @Override
+ public void onBillingInitialized() {
+ readyToPurchase = true;
+ checkStatus();
+ if (!(action != null && action.equals("restore")))
+ getProducts();
+ }
+
+ @Override
+ public void onProductPurchased(String productId, TransactionDetails details) {
+ checkStatus();
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(DonateActivity.this, "Thanks for your support!", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onBillingError(int errorCode, Throwable error) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(DonateActivity.this, "Unable to process purchase", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ }
+
+ @Override
+ public void onPurchaseHistoryRestored() {
+
+ }
+
+ @Override
+ public void onDestroy() {
+ if (bp != null)
+ bp.release();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (!bp.handleActivityResult(requestCode, resultCode, data))
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ private void checkStatus() {
+ new AsyncTask() {
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ List owned = bp.listOwnedProducts();
+ return owned != null && owned.size() != 0;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean b) {
+ super.onPostExecute(b);
+ if (b) {
+ PreferencesUtility.getInstance(DonateActivity.this).setFullUnlocked(true);
+ status.setText("Thanks for your support!");
+ if (action!=null && action.equals("restore")) {
+ status.setText("Your purchases has been restored. Thanks for your support");
+ progressBar.setVisibility(View.GONE);
+ }
+ if (getSupportActionBar() != null)
+ getSupportActionBar().setTitle("Support development");
+ } else {
+ if (action!=null && action.equals("restore")) {
+ status.setText("No previous purchase found");
+ getProducts();
+ }
+ }
+ }
+ }.execute();
+ }
+
+ private void getProducts() {
+
+ new AsyncTask>() {
+ @Override
+ protected List doInBackground(Void... voids) {
+
+ ArrayList products = new ArrayList<>();
+
+ products.add(DONATION_1);
+ products.add(DONATION_2);
+ products.add(DONATION_3);
+ products.add(DONATION_5);
+ products.add(DONATION_10);
+ products.add(DONATION_20);
+
+ return bp.getPurchaseListingDetails(products);
+ }
+
+ @Override
+ protected void onPostExecute(List productList) {
+ super.onPostExecute(productList);
+
+ if (productList == null)
+ return;
+
+ Collections.sort(productList, new Comparator() {
+ @Override
+ public int compare(SkuDetails skuDetails, SkuDetails t1) {
+ if (skuDetails.priceValue >= t1.priceValue)
+ return 1;
+ else if (skuDetails.priceValue <= t1.priceValue)
+ return -1;
+ else return 0;
+ }
+ });
+ for (int i = 0; i < productList.size(); i++) {
+ final SkuDetails product = productList.get(i);
+ View rootView = LayoutInflater.from(DonateActivity.this).inflate(R.layout.item_donate_product, productListView, false);
+
+ TextView detail = (TextView) rootView.findViewById(R.id.product_detail);
+ detail.setText(product.priceText);
+
+ rootView.findViewById(R.id.btn_donate).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (readyToPurchase)
+ bp.purchase(DonateActivity.this, product.productId);
+ else
+ Toast.makeText(DonateActivity.this, "Unable to initiate purchase", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ productListView.addView(rootView);
+
+ }
+ progressBar.setVisibility(View.GONE);
+ }
+ }.execute();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ super.onBackPressed();
+ return true;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/app/src/main/java/com/naman14/timber/activities/MainActivity.java b/app/src/main/java/com/naman14/timber/activities/MainActivity.java
index 69c2c6b56..38df03060 100644
--- a/app/src/main/java/com/naman14/timber/activities/MainActivity.java
+++ b/app/src/main/java/com/naman14/timber/activities/MainActivity.java
@@ -16,34 +16,41 @@
import android.Manifest;
import android.content.Intent;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
-import android.support.design.widget.NavigationView;
-import android.support.design.widget.Snackbar;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v4.view.GravityCompat;
-import android.support.v4.widget.DrawerLayout;
+import com.google.android.material.navigation.NavigationView;
+import com.google.android.material.snackbar.Snackbar;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.core.view.GravityCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+import android.view.Gravity;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;
+import com.anjlab.android.iab.v3.BillingProcessor;
+import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity;
import com.naman14.timber.MusicPlayer;
import com.naman14.timber.R;
+import com.naman14.timber.cast.ExpandedControlsActivity;
import com.naman14.timber.fragments.AlbumDetailFragment;
import com.naman14.timber.fragments.ArtistDetailFragment;
+import com.naman14.timber.fragments.FoldersFragment;
import com.naman14.timber.fragments.MainFragment;
import com.naman14.timber.fragments.PlaylistFragment;
import com.naman14.timber.fragments.QueueFragment;
import com.naman14.timber.permissions.Nammu;
import com.naman14.timber.permissions.PermissionCallback;
import com.naman14.timber.slidinguppanel.SlidingUpPanelLayout;
+import com.naman14.timber.subfragments.LyricsFragment;
import com.naman14.timber.utils.Constants;
import com.naman14.timber.utils.Helpers;
import com.naman14.timber.utils.NavigationUtils;
@@ -56,17 +63,18 @@
public class MainActivity extends BaseActivity implements ATEActivityThemeCustomizer {
+ private SlidingUpPanelLayout panelLayout;
+ private NavigationView navigationView;
+ private TextView songtitle, songartist;
+ private ImageView albumart;
+ private String action;
+ private Map navigationMap = new HashMap();
+ private Handler navDrawerRunnable = new Handler();
+ private Runnable runnable;
+ private DrawerLayout mDrawerLayout;
+ private boolean isDarkTheme;
- private static MainActivity sMainActivity;
- SlidingUpPanelLayout panelLayout;
- NavigationView navigationView;
- TextView songtitle, songartist;
- ImageView albumart;
- String action;
- Map navigationMap = new HashMap();
- Handler navDrawerRunnable = new Handler();
- Runnable runnable;
- Runnable navigateLibrary = new Runnable() {
+ private Runnable navigateLibrary = new Runnable() {
public void run() {
navigationView.getMenu().findItem(R.id.nav_library).setChecked(true);
Fragment fragment = new MainFragment();
@@ -75,34 +83,30 @@ public void run() {
}
};
- Runnable navigateNowplaying = new Runnable() {
+
+ private Runnable navigatePlaylist = new Runnable() {
public void run() {
- navigateLibrary.run();
- startActivity(new Intent(MainActivity.this, NowPlayingActivity.class));
- }
- };
- final PermissionCallback permissionReadstorageCallback = new PermissionCallback() {
- @Override
- public void permissionGranted() {
- loadEverything();
- }
+ navigationView.getMenu().findItem(R.id.nav_playlists).setChecked(true);
+ Fragment fragment = new PlaylistFragment();
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));
+ transaction.replace(R.id.fragment_container, fragment).commit();
- @Override
- public void permissionRefused() {
- finish();
}
};
- Runnable navigatePlaylist = new Runnable() {
+
+ private Runnable navigateFolder = new Runnable() {
public void run() {
- navigationView.getMenu().findItem(R.id.nav_playlists).setChecked(true);
- Fragment fragment = new PlaylistFragment();
+ navigationView.getMenu().findItem(R.id.nav_folders).setChecked(true);
+ Fragment fragment = new FoldersFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.hide(getSupportFragmentManager().findFragmentById(R.id.fragment_container));
transaction.replace(R.id.fragment_container, fragment).commit();
}
};
- Runnable navigateQueue = new Runnable() {
+
+ private Runnable navigateQueue = new Runnable() {
public void run() {
navigationView.getMenu().findItem(R.id.nav_queue).setChecked(true);
Fragment fragment = new QueueFragment();
@@ -112,7 +116,8 @@ public void run() {
}
};
- Runnable navigateAlbum = new Runnable() {
+
+ private Runnable navigateAlbum = new Runnable() {
public void run() {
long albumID = getIntent().getExtras().getLong(Constants.ALBUM_ID);
Fragment fragment = AlbumDetailFragment.newInstance(albumID, false, null);
@@ -121,7 +126,8 @@ public void run() {
.replace(R.id.fragment_container, fragment).commit();
}
};
- Runnable navigateArtist = new Runnable() {
+
+ private Runnable navigateArtist = new Runnable() {
public void run() {
long artistID = getIntent().getExtras().getLong(Constants.ARTIST_ID);
Fragment fragment = ArtistDetailFragment.newInstance(artistID, false, null);
@@ -130,17 +136,39 @@ public void run() {
.replace(R.id.fragment_container, fragment).commit();
}
};
- private DrawerLayout mDrawerLayout;
- private boolean isDarkTheme;
- public static MainActivity getInstance() {
- return sMainActivity;
- }
+ private Runnable navigateLyrics = new Runnable() {
+ public void run() {
+ Fragment fragment = new LyricsFragment();
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ fragmentManager.beginTransaction()
+ .replace(R.id.fragment_container, fragment).commit();
+ }
+ };
+
+ private Runnable navigateNowplaying = new Runnable() {
+ public void run() {
+ navigateLibrary.run();
+ startActivity(new Intent(MainActivity.this, NowPlayingActivity.class));
+ }
+ };
+
+ private final PermissionCallback permissionReadstorageCallback = new PermissionCallback() {
+ @Override
+ public void permissionGranted() {
+ loadEverything();
+ }
+
+ @Override
+ public void permissionRefused() {
+ finish();
+ }
+ };
+
@Override
public void onCreate(Bundle savedInstanceState) {
- sMainActivity = this;
action = getIntent().getAction();
isDarkTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false);
@@ -154,6 +182,7 @@ public void onCreate(Bundle savedInstanceState) {
navigationMap.put(Constants.NAVIGATE_NOWPLAYING, navigateNowplaying);
navigationMap.put(Constants.NAVIGATE_ALBUM, navigateAlbum);
navigationMap.put(Constants.NAVIGATE_ARTIST, navigateArtist);
+ navigationMap.put(Constants.NAVIGATE_LYRICS, navigateLyrics);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
panelLayout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);
@@ -178,11 +207,49 @@ public void run() {
if (TimberUtils.isMarshmallow()) {
checkPermissionAndThenLoad();
+ //checkWritePermissions();
} else {
loadEverything();
}
addBackstackListener();
+
+ if(Intent.ACTION_VIEW.equals(action)) {
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ MusicPlayer.clearQueue();
+ MusicPlayer.openFile(getIntent().getData().getPath());
+ MusicPlayer.playOrPause();
+ navigateNowplaying.run();
+ }
+ }, 350);
+ }
+
+ if (!panelLayout.isPanelHidden() && MusicPlayer.getTrackName() == null ) {
+ panelLayout.hidePanel();
+ }
+
+ if (playServicesAvailable) {
+
+ final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT);
+ params.gravity = Gravity.BOTTOM;
+
+ FrameLayout contentRoot = findViewById(R.id.content_root);
+ contentRoot.addView(LayoutInflater.from(this)
+ .inflate(R.layout.fragment_cast_mini_controller, null), params);
+
+ findViewById(R.id.castMiniController).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(MainActivity.this, ExpandedControllerActivity.class));
+ }
+ });
+ }
+
}
private void loadEverything() {
@@ -198,7 +265,7 @@ private void loadEverything() {
private void checkPermissionAndThenLoad() {
//check for permission
- if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
+ if (Nammu.checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE) && Nammu.checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
loadEverything();
} else {
if (Nammu.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
@@ -207,18 +274,20 @@ private void checkPermissionAndThenLoad() {
.setAction("OK", new View.OnClickListener() {
@Override
public void onClick(View view) {
- Nammu.askForPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE, permissionReadstorageCallback);
+ Nammu.askForPermission(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, permissionReadstorageCallback);
}
}).show();
} else {
- Nammu.askForPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE, permissionReadstorageCallback);
+ Nammu.askForPermission(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, permissionReadstorageCallback);
}
}
}
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
+
return true;
}
@@ -237,13 +306,13 @@ public boolean onOptionsItemSelected(MenuItem item) {
@Override
public void onBackPressed() {
-
- if (panelLayout.isPanelExpanded())
+ if (panelLayout.isPanelExpanded()) {
panelLayout.collapsePanel();
- else {
+ } else if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
+ mDrawerLayout.closeDrawer(GravityCompat.START);
+ } else {
super.onBackPressed();
}
-
}
private void setupDrawerContent(NavigationView navigationView) {
@@ -268,18 +337,28 @@ private void setupNavigationIcons(NavigationView navigationView) {
navigationView.getMenu().findItem(R.id.nav_library).setIcon(R.drawable.library_music);
navigationView.getMenu().findItem(R.id.nav_playlists).setIcon(R.drawable.playlist_play);
navigationView.getMenu().findItem(R.id.nav_queue).setIcon(R.drawable.music_note);
+ navigationView.getMenu().findItem(R.id.nav_folders).setIcon(R.drawable.ic_folder_open_black_24dp);
navigationView.getMenu().findItem(R.id.nav_nowplaying).setIcon(R.drawable.bookmark_music);
navigationView.getMenu().findItem(R.id.nav_settings).setIcon(R.drawable.settings);
- navigationView.getMenu().findItem(R.id.nav_help).setIcon(R.drawable.help_circle);
navigationView.getMenu().findItem(R.id.nav_about).setIcon(R.drawable.information);
+ navigationView.getMenu().findItem(R.id.nav_donate).setIcon(R.drawable.payment_black);
} else {
navigationView.getMenu().findItem(R.id.nav_library).setIcon(R.drawable.library_music_white);
navigationView.getMenu().findItem(R.id.nav_playlists).setIcon(R.drawable.playlist_play_white);
navigationView.getMenu().findItem(R.id.nav_queue).setIcon(R.drawable.music_note_white);
+ navigationView.getMenu().findItem(R.id.nav_folders).setIcon(R.drawable.ic_folder_open_white_24dp);
navigationView.getMenu().findItem(R.id.nav_nowplaying).setIcon(R.drawable.bookmark_music_white);
navigationView.getMenu().findItem(R.id.nav_settings).setIcon(R.drawable.settings_white);
- navigationView.getMenu().findItem(R.id.nav_help).setIcon(R.drawable.help_circle_white);
navigationView.getMenu().findItem(R.id.nav_about).setIcon(R.drawable.information_white);
+ navigationView.getMenu().findItem(R.id.nav_donate).setIcon(R.drawable.payment_white);
+ }
+
+ try {
+ if (!BillingProcessor.isIabServiceAvailable(this)) {
+ navigationView.getMenu().removeItem(R.id.nav_donate);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
}
}
@@ -295,9 +374,17 @@ private void updatePosition(final MenuItem menuItem) {
case R.id.nav_playlists:
runnable = navigatePlaylist;
+ break;
+ case R.id.nav_folders:
+ runnable = navigateFolder;
+
break;
case R.id.nav_nowplaying:
- NavigationUtils.navigateToNowplaying(MainActivity.this, false);
+ if (getCastSession() != null) {
+ startActivity(new Intent(MainActivity.this, ExpandedControlsActivity.class));
+ } else {
+ NavigationUtils.navigateToNowplaying(MainActivity.this, false);
+ }
break;
case R.id.nav_queue:
runnable = navigateQueue;
@@ -306,12 +393,6 @@ private void updatePosition(final MenuItem menuItem) {
case R.id.nav_settings:
NavigationUtils.navigateToSettings(MainActivity.this);
break;
- case R.id.nav_help:
- Intent intent = new Intent(Intent.ACTION_VIEW);
- Uri data = Uri.parse("mailto:namandwivedi14@gmail.com");
- intent.setData(data);
- startActivity(intent);
- break;
case R.id.nav_about:
mDrawerLayout.closeDrawers();
Handler handler = new Handler();
@@ -322,6 +403,9 @@ public void run() {
}
}, 350);
+ break;
+ case R.id.nav_donate:
+ startActivity(new Intent(MainActivity.this, DonateActivity.class));
break;
}
@@ -357,12 +441,10 @@ public void setDetailsToHeader() {
public void onMetaChanged() {
super.onMetaChanged();
setDetailsToHeader();
- }
- @Override
- public void onResume() {
- super.onResume();
- sMainActivity = this;
+ if (panelLayout.isPanelHidden() && MusicPlayer.getTrackName() != null) {
+ panelLayout.showPanel();
+ }
}
@Override
@@ -374,7 +456,7 @@ public void onRequestPermissionsResult(
private boolean isNavigatingMain() {
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
return (currentFragment instanceof MainFragment || currentFragment instanceof QueueFragment
- || currentFragment instanceof PlaylistFragment);
+ || currentFragment instanceof PlaylistFragment || currentFragment instanceof FoldersFragment);
}
private void addBackstackListener() {
@@ -392,6 +474,28 @@ public int getActivityTheme() {
return isDarkTheme ? R.style.AppThemeNormalDark : R.style.AppThemeNormalLight;
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ getSupportFragmentManager().findFragmentById(R.id.fragment_container).onActivityResult(requestCode, resultCode, data);
+ }
+
+
+ @Override
+ public void showCastMiniController() {
+ findViewById(R.id.castMiniController).setVisibility(View.VISIBLE);
+ findViewById(R.id.quickcontrols_container).setVisibility(View.GONE);
+ panelLayout.hidePanel();
+ }
+
+ @Override
+ public void hideCastMiniController() {
+
+ findViewById(R.id.castMiniController).setVisibility(View.GONE);
+ findViewById(R.id.quickcontrols_container).setVisibility(View.VISIBLE);
+
+ panelLayout.showPanel();
+ }
}
diff --git a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java
index 6cd258f0b..46cd4bf2c 100644
--- a/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java
+++ b/app/src/main/java/com/naman14/timber/activities/NowPlayingActivity.java
@@ -5,12 +5,13 @@
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.annotation.StyleRes;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
+import androidx.annotation.StyleRes;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
import com.afollestad.appthemeengine.Config;
import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;
+import com.afollestad.appthemeengine.customizers.ATEStatusBarCustomizer;
import com.afollestad.appthemeengine.customizers.ATEToolbarCustomizer;
import com.naman14.timber.R;
import com.naman14.timber.utils.Constants;
@@ -20,7 +21,7 @@
/**
* Created by naman on 01/01/16.
*/
-public class NowPlayingActivity extends BaseActivity implements ATEActivityThemeCustomizer, ATEToolbarCustomizer {
+public class NowPlayingActivity extends BaseActivity implements ATEActivityThemeCustomizer, ATEToolbarCustomizer, ATEStatusBarCustomizer {
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -41,7 +42,6 @@ public void onCreate(Bundle savedInstanceState) {
@Override
public int getActivityTheme() {
return PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false) ? R.style.AppTheme_FullScreen_Dark : R.style.AppTheme_FullScreen_Light;
-
}
@Override
@@ -49,11 +49,21 @@ public int getLightToolbarMode() {
return Config.LIGHT_TOOLBAR_AUTO;
}
+ @Override
+ public int getLightStatusBarMode() {
+ return Config.LIGHT_STATUS_BAR_OFF;
+ }
+
@Override
public int getToolbarColor() {
return Color.TRANSPARENT;
}
+ @Override
+ public int getStatusBarColor() {
+ return Color.TRANSPARENT;
+ }
+
@Override
public void onResume() {
super.onResume();
diff --git a/app/src/main/java/com/naman14/timber/activities/PlaylistDetailActivity.java b/app/src/main/java/com/naman14/timber/activities/PlaylistDetailActivity.java
index 8479fbb73..a02ea6f02 100644
--- a/app/src/main/java/com/naman14/timber/activities/PlaylistDetailActivity.java
+++ b/app/src/main/java/com/naman14/timber/activities/PlaylistDetailActivity.java
@@ -15,75 +15,91 @@
package com.naman14.timber.activities;
import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
-import android.support.annotation.StyleRes;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
+import android.provider.MediaStore;
+import androidx.annotation.NonNull;
+import androidx.annotation.StyleRes;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.appcompat.widget.Toolbar;
import android.transition.Transition;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import com.afollestad.appthemeengine.Config;
import com.afollestad.appthemeengine.customizers.ATEActivityThemeCustomizer;
+import com.afollestad.appthemeengine.customizers.ATEToolbarCustomizer;
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
import com.naman14.timber.R;
import com.naman14.timber.adapters.SongsListAdapter;
import com.naman14.timber.dataloaders.LastAddedLoader;
+import com.naman14.timber.dataloaders.PlaylistLoader;
import com.naman14.timber.dataloaders.PlaylistSongLoader;
import com.naman14.timber.dataloaders.SongLoader;
import com.naman14.timber.dataloaders.TopTracksLoader;
import com.naman14.timber.listeners.SimplelTransitionListener;
import com.naman14.timber.models.Song;
import com.naman14.timber.utils.Constants;
-import com.naman14.timber.utils.PreferencesUtility;
import com.naman14.timber.utils.TimberUtils;
import com.naman14.timber.widgets.DividerItemDecoration;
+import com.naman14.timber.widgets.DragSortRecycler;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.util.HashMap;
import java.util.List;
-public class PlaylistDetailActivity extends BaseThemedActivity implements ATEActivityThemeCustomizer {
+public class PlaylistDetailActivity extends BaseActivity implements ATEActivityThemeCustomizer, ATEToolbarCustomizer {
- String action;
- long playlistID;
- HashMap playlistsMap = new HashMap<>();
- Runnable playlistLastAdded = new Runnable() {
+ private String action;
+ private long playlistID;
+ private HashMap playlistsMap = new HashMap<>();
+
+ private AppCompatActivity mContext = PlaylistDetailActivity.this;
+ private SongsListAdapter mAdapter;
+ private RecyclerView recyclerView;
+ private ImageView blurFrame;
+ private TextView playlistname;
+ private View foreground;
+ private boolean animate;
+
+ private Runnable playlistLastAdded = new Runnable() {
public void run() {
new loadLastAdded().execute("");
}
};
- Runnable playlistRecents = new Runnable() {
+ private Runnable playlistRecents = new Runnable() {
@Override
public void run() {
new loadRecentlyPlayed().execute("");
}
};
- Runnable playlistToptracks = new Runnable() {
+ private Runnable playlistToptracks = new Runnable() {
@Override
public void run() {
new loadTopTracks().execute("");
}
};
- Runnable playlistUsercreated = new Runnable() {
+ private Runnable playlistUsercreated = new Runnable() {
@Override
public void run() {
new loadUserCreatedPlaylist().execute("");
}
};
- private AppCompatActivity mContext = PlaylistDetailActivity.this;
- private SongsListAdapter mAdapter;
- private RecyclerView recyclerView;
- private ImageView blurFrame;
- private TextView playlistname;
- private View foreground;
@TargetApi(21)
@Override
@@ -94,6 +110,12 @@ public void onCreate(Bundle savedInstanceState) {
action = getIntent().getAction();
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle("");
+
playlistsMap.put(Constants.NAVIGATE_PLAYLIST_LASTADDED, playlistLastAdded);
playlistsMap.put(Constants.NAVIGATE_PLAYLIST_RECENT, playlistRecents);
playlistsMap.put(Constants.NAVIGATE_PLAYLIST_TOPTRACKS, playlistToptracks);
@@ -108,12 +130,23 @@ public void onCreate(Bundle savedInstanceState) {
setAlbumart();
- if (TimberUtils.isLollipop() && PreferencesUtility.getInstance(this).getAnimations()) {
- getWindow().getEnterTransition().addListener(new EnterTransitionListener());
+ animate = getIntent().getBooleanExtra(Constants.ACTIVITY_TRANSITION, false);
+ if (animate && TimberUtils.isLollipop()) {
+ if(savedInstanceState != null && savedInstanceState.containsKey("ROTATION_RECREATION")){
+ setUpSongs();
+ }
+ else{
+ getWindow().getEnterTransition().addListener(new EnterTransitionListener());
+ }
} else {
setUpSongs();
}
+ }
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString("ROTATION_RECREATION", "Rotacion");
}
private void setAlbumart() {
@@ -126,6 +159,27 @@ private void setUpSongs() {
Runnable navigation = playlistsMap.get(action);
if (navigation != null) {
navigation.run();
+
+ DragSortRecycler dragSortRecycler = new DragSortRecycler();
+ dragSortRecycler.setViewHandleId(R.id.reorder);
+
+ dragSortRecycler.setOnItemMovedListener(new DragSortRecycler.OnItemMovedListener() {
+ @Override
+ public void onItemMoved(int from, int to) {
+ Log.d("playlist", "onItemMoved " + from + " to " + to);
+ Song song = mAdapter.getSongAt(from);
+ mAdapter.removeSongAt(from);
+ mAdapter.addSongTo(to, song);
+ mAdapter.notifyDataSetChanged();
+ MediaStore.Audio.Playlists.Members.moveItem(getContentResolver(),
+ playlistID, from, to);
+ }
+ });
+
+ recyclerView.addItemDecoration(dragSortRecycler);
+ recyclerView.addOnItemTouchListener(dragSortRecycler);
+ recyclerView.addOnScrollListener(dragSortRecycler.getScrollListener());
+
} else {
Log.d("PlaylistDetail", "mo action specified");
}
@@ -141,7 +195,7 @@ private void loadBitmap(String uri) {
private void setRecyclerViewAapter() {
recyclerView.setAdapter(mAdapter);
- if (TimberUtils.isLollipop() && PreferencesUtility.getInstance(mContext).getAnimations()) {
+ if (animate && TimberUtils.isLollipop()) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
@@ -165,7 +219,8 @@ private class loadLastAdded extends AsyncTask {
@Override
protected String doInBackground(String... params) {
List lastadded = LastAddedLoader.getLastAddedSongs(mContext);
- mAdapter = new SongsListAdapter(mContext, lastadded, true);
+ mAdapter = new SongsListAdapter(mContext, lastadded, true, animate);
+ mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@@ -185,7 +240,8 @@ private class loadRecentlyPlayed extends AsyncTask {
protected String doInBackground(String... params) {
TopTracksLoader loader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.RecentSongs);
List recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());
- mAdapter = new SongsListAdapter(mContext, recentsongs, true);
+ mAdapter = new SongsListAdapter(mContext, recentsongs, true, animate);
+ mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@@ -206,7 +262,8 @@ private class loadTopTracks extends AsyncTask {
protected String doInBackground(String... params) {
TopTracksLoader loader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.TopTracks);
List toptracks = SongLoader.getSongsForCursor(TopTracksLoader.getCursor());
- mAdapter = new SongsListAdapter(mContext, toptracks, true);
+ mAdapter = new SongsListAdapter(mContext, toptracks, true, animate);
+ mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@@ -226,7 +283,8 @@ private class loadUserCreatedPlaylist extends AsyncTask {
protected String doInBackground(String... params) {
playlistID = getIntent().getExtras().getLong(Constants.PLAYLIST_ID);
List playlistsongs = PlaylistSongLoader.getSongsInPlaylist(mContext, playlistID);
- mAdapter = new SongsListAdapter(mContext, playlistsongs, true);
+ mAdapter = new SongsListAdapter(mContext, playlistsongs, true, animate);
+ mAdapter.setPlaylistId(playlistID);
return "Executed";
}
@@ -252,5 +310,99 @@ public void onTransitionStart(Transition paramTransition) {
}
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+
+ getMenuInflater().inflate(R.menu.menu_playlist_detail, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (action.equals(Constants.NAVIGATE_PLAYLIST_USERCREATED)) {
+ menu.findItem(R.id.action_delete_playlist).setVisible(true);
+ menu.findItem(R.id.action_clear_auto_playlist).setVisible(false);
+ } else {
+ menu.findItem(R.id.action_delete_playlist).setVisible(false);
+ menu.findItem(R.id.action_clear_auto_playlist).setTitle("Clear " + playlistname.getText().toString());
+ }
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ super.onBackPressed();
+ return true;
+ case R.id.action_delete_playlist:
+ showDeletePlaylistDialog();
+ break;
+ case R.id.action_clear_auto_playlist:
+ clearAutoPlaylists();
+ break;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ private void showDeletePlaylistDialog() {
+ new MaterialDialog.Builder(this)
+ .title("Delete playlist?")
+ .content("Are you sure you want to delete playlist " + playlistname.getText().toString() + " ?")
+ .positiveText("Delete")
+ .negativeText("Cancel")
+ .onPositive(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ PlaylistLoader.deletePlaylists(PlaylistDetailActivity.this, playlistID);
+ Intent returnIntent = new Intent();
+ setResult(Activity.RESULT_OK, returnIntent);
+ finish();
+ }
+ })
+ .onNegative(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ dialog.dismiss();
+ }
+ })
+ .show();
+ }
+
+ private void clearAutoPlaylists() {
+ switch (action) {
+ case Constants.NAVIGATE_PLAYLIST_LASTADDED:
+ TimberUtils.clearLastAdded(this);
+ break;
+ case Constants.NAVIGATE_PLAYLIST_RECENT:
+ TimberUtils.clearRecent(this);
+ break;
+ case Constants.NAVIGATE_PLAYLIST_TOPTRACKS:
+ TimberUtils.clearTopTracks(this);
+ break;
+ }
+ Intent returnIntent = new Intent();
+ setResult(Activity.RESULT_OK, returnIntent);
+ finish();
+ }
+
+ @Override
+ public void onMetaChanged() {
+ super.onMetaChanged();
+ if (mAdapter != null)
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public int getToolbarColor() {
+ return Color.TRANSPARENT;
+ }
+
+ @Override
+ public int getLightToolbarMode() {
+ return Config.LIGHT_TOOLBAR_AUTO;
+ }
}
diff --git a/app/src/main/java/com/naman14/timber/activities/SearchActivity.java b/app/src/main/java/com/naman14/timber/activities/SearchActivity.java
index 2ddebb8d4..2c114bc6e 100644
--- a/app/src/main/java/com/naman14/timber/activities/SearchActivity.java
+++ b/app/src/main/java/com/naman14/timber/activities/SearchActivity.java
@@ -15,12 +15,15 @@
package com.naman14.timber.activities;
import android.content.Context;
+import android.os.AsyncTask;
import android.os.Bundle;
-import android.support.v4.view.MenuItemCompat;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.SearchView;
-import android.support.v7.widget.Toolbar;
+import androidx.annotation.Nullable;
+import androidx.core.view.MenuItemCompat;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.appcompat.widget.SearchView;
+import androidx.appcompat.widget.Toolbar;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
@@ -38,11 +41,16 @@
import com.naman14.timber.provider.SearchHistory;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
-public class SearchActivity extends BaseThemedActivity implements SearchView.OnQueryTextListener, View.OnTouchListener {
+public class SearchActivity extends BaseActivity implements SearchView.OnQueryTextListener, View.OnTouchListener {
+
+ private final Executor mSearchExecutor = Executors.newSingleThreadExecutor();
+ @Nullable
+ private AsyncTask mSearchTask = null;
private SearchView mSearchView;
private InputMethodManager mImm;
@@ -51,7 +59,9 @@ public class SearchActivity extends BaseThemedActivity implements SearchView.OnQ
private SearchAdapter adapter;
private RecyclerView recyclerView;
- private List searchResults = Collections.emptyList();
+ private List