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 [![Build Status](https://travis-ci.org/naman14/Timber.svg?branch=master)](https://travis-ci.org/naman14/Timber) -[WIP][BETA]-Material Design Music Player +Material Design Music Player -[![Get it on Google Play](https://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=naman14.timber) -[![Get it on F-Droid](https://guardianproject.info/wp-content/uploads/2014/07/logo-fdroid.png)](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 - -![](https://raw.githubusercontent.com/naman14/Timber/master/graphics/Screenshot_2015-09-18-12-33-27.png) -![](https://raw.githubusercontent.com/naman14/Timber/master/graphics/Screenshot_2015-08-05-14-23-03.png) -![](https://raw.githubusercontent.com/naman14/Timber/master/graphics/Screenshot_2015-08-29-22-44-26.png) -![](https://raw.githubusercontent.com/naman14/Timber/master/graphics/Screenshot_2015-08-31-11-50-50.png) -![](https://raw.githubusercontent.com/naman14/Timber/master/graphics/Screenshot_2015-08-31-11-52-50.png) +Get it on Google Play -## Contribute -### Translations +Get it on F-Droid -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 searchResults = Collections.emptyList(); + + Bundle bundle; @Override public void onCreate(Bundle savedInstanceState) { @@ -69,6 +79,18 @@ public void onCreate(Bundle savedInstanceState) { recyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter = new SearchAdapter(this); recyclerView.setAdapter(adapter); + + if(savedInstanceState != null && savedInstanceState.containsKey("QUERY_STRING")){ + bundle = savedInstanceState; + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (queryString != null){ + outState.putString("QUERY_STRING", queryString); + } } @@ -99,9 +121,23 @@ public boolean onMenuItemActionCollapse(MenuItem item) { }); menu.findItem(R.id.menu_search).expandActionView(); + + if(bundle != null && bundle.containsKey("QUERY_STRING")){ + mSearchView.setQuery(bundle.getString("QUERY_STRING"), true); + } + return super.onCreateOptionsMenu(menu); } + + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem item = menu.findItem(R.id.action_search); + item.setVisible(false); + return true; + } + @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { @@ -128,34 +164,20 @@ public boolean onQueryTextChange(final String newText) { if (newText.equals(queryString)) { return true; } + if (mSearchTask != null) { + mSearchTask.cancel(false); + mSearchTask = null; + } queryString = newText; - if (!queryString.trim().equals("")) { - this.searchResults = new ArrayList(); - List songList = SongLoader.searchSongs(this, queryString); - List albumList = AlbumLoader.getAlbums(this, queryString); - List artistList = ArtistLoader.getArtists(this, queryString); - - if (!songList.isEmpty()) { - searchResults.add("Songs"); - } - searchResults.addAll((Collection) (songList.size() < 10 ? songList : songList.subList(0, 10))); - if (!albumList.isEmpty()) { - searchResults.add("Albums"); - } - searchResults.addAll((Collection) (albumList.size() < 7 ? albumList : albumList.subList(0, 7))); - if (!artistList.isEmpty()) { - searchResults.add("Artists"); - } - searchResults.addAll((Collection) (artistList.size() < 7 ? artistList : artistList.subList(0, 7))); - } else { + if (queryString.trim().equals("")) { searchResults.clear(); adapter.updateSearchResults(searchResults); adapter.notifyDataSetChanged(); + } else { + mSearchTask = new SearchTask().executeOnExecutor(mSearchExecutor, queryString); + Log.d("AAAABBBBBB", "TaskCanelled? " + (mSearchTask.isCancelled())); } - adapter.updateSearchResults(searchResults); - adapter.notifyDataSetChanged(); - return true; } @@ -165,6 +187,14 @@ public boolean onTouch(View v, MotionEvent event) { return false; } + @Override + protected void onDestroy() { + if (mSearchTask != null && mSearchTask.getStatus() != AsyncTask.Status.FINISHED) { + mSearchTask.cancel(false); + } + super.onDestroy(); + } + public void hideInputManager() { if (mSearchView != null) { if (mImm != null) { @@ -175,4 +205,50 @@ public void hideInputManager() { SearchHistory.getInstance(this).addSearchString(queryString); } } + + private class SearchTask extends AsyncTask> { + + @Override + protected ArrayList doInBackground(String... params) { + ArrayList results = new ArrayList<>(27); + List songList = SongLoader.searchSongs(SearchActivity.this, params[0], 10); + if (!songList.isEmpty()) { + results.add(getString(R.string.songs)); + results.addAll(songList); + } + boolean canceled = isCancelled(); + if (canceled) { + return null; + } + List albumList = AlbumLoader.getAlbums(SearchActivity.this, params[0], 7); + if (!albumList.isEmpty()) { + results.add(getString(R.string.albums)); + results.addAll(albumList); + } + + canceled = isCancelled(); + if (canceled) { + return null; + } + List artistList = ArtistLoader.getArtists(SearchActivity.this, params[0], 7); + if (!artistList.isEmpty()) { + results.add(getString(R.string.artists)); + results.addAll(artistList); + } + if (results.size() == 0) { + results.add(getString(R.string.nothing_found)); + } + return results; + } + + @Override + protected void onPostExecute(ArrayList objects) { + super.onPostExecute(objects); + mSearchTask = null; + if (objects != null) { + adapter.updateSearchResults(objects); + adapter.notifyDataSetChanged(); + } + } + } } diff --git a/app/src/main/java/com/naman14/timber/activities/SettingsActivity.java b/app/src/main/java/com/naman14/timber/activities/SettingsActivity.java index 47f5a0761..5c1be7f96 100644 --- a/app/src/main/java/com/naman14/timber/activities/SettingsActivity.java +++ b/app/src/main/java/com/naman14/timber/activities/SettingsActivity.java @@ -17,12 +17,12 @@ import android.os.Bundle; import android.preference.PreferenceFragment; import android.preference.PreferenceManager; -import android.support.annotation.ColorInt; -import android.support.annotation.NonNull; -import android.support.annotation.StyleRes; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v7.widget.Toolbar; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.StyleRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.appcompat.widget.Toolbar; import android.view.MenuItem; import com.afollestad.appthemeengine.ATE; @@ -37,7 +37,7 @@ public class SettingsActivity extends BaseThemedActivity implements ColorChooserDialog.ColorCallback, ATEActivityThemeCustomizer { - String action; + private String action; @Override public void onCreate(Bundle savedInstanceState) { diff --git a/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java b/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java index b88dcef2b..e9486254a 100644 --- a/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/AlbumAdapter.java @@ -16,8 +16,8 @@ import android.app.Activity; import android.graphics.Bitmap; -import android.support.v7.graphics.Palette; -import android.support.v7.widget.RecyclerView; +import androidx.palette.graphics.Palette; +import androidx.recyclerview.widget.RecyclerView; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; @@ -25,13 +25,16 @@ import android.widget.ImageView; import android.widget.TextView; +import com.afollestad.appthemeengine.Config; import com.naman14.timber.R; import com.naman14.timber.models.Album; +import com.naman14.timber.utils.Helpers; import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.TimberUtils; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; @@ -72,7 +75,7 @@ public void onBindViewHolder(final ItemHolder itemHolder, int i) { ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.id).toString(), itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true) - .showImageOnFail(R.drawable.ic_empty_music2) + .showImageOnLoading(R.drawable.ic_empty_music2) .resetViewBeforeLoading(true) .displayer(new FadeInBitmapDisplayer(400)) .build(), new SimpleImageLoadingListener() { @@ -106,6 +109,18 @@ public void onGenerated(Palette palette) { } } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + if (isGrid) { + itemHolder.footer.setBackgroundColor(0); + if (mContext != null) { + int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext)); + itemHolder.title.setTextColor(textColorPrimary); + itemHolder.artist.setTextColor(textColorPrimary); + } + } + } }); if (TimberUtils.isLollipop()) diff --git a/app/src/main/java/com/naman14/timber/adapters/AlbumSongsAdapter.java b/app/src/main/java/com/naman14/timber/adapters/AlbumSongsAdapter.java index ed18d84e4..12d9b30ec 100644 --- a/app/src/main/java/com/naman14/timber/adapters/AlbumSongsAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/AlbumSongsAdapter.java @@ -16,8 +16,8 @@ import android.app.Activity; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -35,7 +35,7 @@ import java.util.List; -public class AlbumSongsAdapter extends RecyclerView.Adapter { +public class AlbumSongsAdapter extends BaseSongAdapter { private List arraylist; private Activity mContext; @@ -109,6 +109,13 @@ public boolean onMenuItemClick(MenuItem item) { case R.id.popup_song_addto_playlist: AddPlaylistDialog.newInstance(arraylist.get(position)).show(((AppCompatActivity) mContext).getSupportFragmentManager(), "ADD_PLAYLIST"); break; + case R.id.popup_song_share: + TimberUtils.shareTrack(mContext, arraylist.get(position).id); + break; + case R.id.popup_song_delete: + long[] deleteIds = {arraylist.get(position).id}; + TimberUtils.showDeleteDialog(mContext,arraylist.get(position).title, deleteIds, AlbumSongsAdapter.this, position); + break; } return false; } @@ -133,11 +140,17 @@ public long[] getSongIds() { return ret; } + @Override public void updateDataSet(List arraylist) { this.arraylist = arraylist; this.songIDs = getSongIds(); } + @Override + public void removeSongAt(int i){ + arraylist.remove(i); + } + public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { protected TextView title, duration, trackNumber; protected ImageView menu; @@ -157,8 +170,9 @@ public void onClick(View v) { handler.postDelayed(new Runnable() { @Override public void run() { - MusicPlayer.playAll(mContext, songIDs, getAdapterPosition(), albumID, TimberUtils.IdType.Album, false); - NavigationUtils.navigateToNowplaying(mContext, true); + playAll(mContext, songIDs, getAdapterPosition(), albumID, + TimberUtils.IdType.Album, false, + arraylist.get(getAdapterPosition()), true); } }, 100); diff --git a/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java b/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java index fa1d59b32..6706bc8a9 100644 --- a/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/ArtistAdapter.java @@ -17,9 +17,9 @@ import android.app.Activity; import android.graphics.Bitmap; import android.graphics.Color; -import android.support.annotation.ColorInt; -import android.support.v7.graphics.Palette; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.ColorInt; +import androidx.palette.graphics.Palette; +import androidx.recyclerview.widget.RecyclerView; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; @@ -27,18 +27,21 @@ import android.widget.ImageView; import android.widget.TextView; +import com.afollestad.appthemeengine.Config; import com.naman14.timber.R; import com.naman14.timber.lastfmapi.LastFmClient; import com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener; import com.naman14.timber.lastfmapi.models.ArtistQuery; import com.naman14.timber.lastfmapi.models.LastfmArtist; import com.naman14.timber.models.Artist; +import com.naman14.timber.utils.Helpers; import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.TimberUtils; import com.naman14.timber.widgets.BubbleTextGetter; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; @@ -91,7 +94,7 @@ public void artistInfoSucess(LastfmArtist artist) { ImageLoader.getInstance().displayImage(artist.mArtwork.get(2).mUrl, itemHolder.artistImage, new DisplayImageOptions.Builder().cacheInMemory(true) .cacheOnDisk(true) - .showImageOnFail(R.drawable.ic_empty_music2) + .showImageOnLoading(R.drawable.ic_empty_music2) .resetViewBeforeLoading(true) .displayer(new FadeInBitmapDisplayer(400)) .build(), new SimpleImageLoadingListener() { @@ -116,12 +119,24 @@ public void onGenerated(Palette palette) { } } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + if (isGrid) { + itemHolder.footer.setBackgroundColor(0); + if (mContext != null) { + int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext)); + itemHolder.name.setTextColor(textColorPrimary); + itemHolder.albums.setTextColor(textColorPrimary); + } + } + } }); } else { ImageLoader.getInstance().displayImage(artist.mArtwork.get(1).mUrl, itemHolder.artistImage, new DisplayImageOptions.Builder().cacheInMemory(true) .cacheOnDisk(true) - .showImageOnFail(R.drawable.ic_empty_music2) + .showImageOnLoading(R.drawable.ic_empty_music2) .resetViewBeforeLoading(true) .displayer(new FadeInBitmapDisplayer(400)) .build()); @@ -140,6 +155,11 @@ public void artistInfoFailed() { } + @Override + public long getItemId(int position) { + return arraylist.get(position).id; + } + @Override public int getItemCount() { return (null != arraylist ? arraylist.size() : 0); diff --git a/app/src/main/java/com/naman14/timber/adapters/ArtistAlbumAdapter.java b/app/src/main/java/com/naman14/timber/adapters/ArtistAlbumAdapter.java index 0cfccf2ef..e656e47c4 100644 --- a/app/src/main/java/com/naman14/timber/adapters/ArtistAlbumAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/ArtistAlbumAdapter.java @@ -15,8 +15,8 @@ package com.naman14.timber.adapters; import android.app.Activity; -import android.support.v7.widget.CardView; -import android.support.v7.widget.RecyclerView; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.RecyclerView; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; @@ -26,10 +26,9 @@ import com.naman14.timber.R; import com.naman14.timber.models.Album; +import com.naman14.timber.utils.ImageUtils; import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.TimberUtils; -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; import java.util.List; @@ -60,11 +59,7 @@ public void onBindViewHolder(ItemHolder itemHolder, int i) { String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, localItem.songCount); itemHolder.details.setText(songCount); - ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.id).toString(), itemHolder.albumArt, - new DisplayImageOptions.Builder().cacheInMemory(true) - .showImageOnFail(R.drawable.ic_empty_music2) - .resetViewBeforeLoading(true) - .build()); + ImageUtils.loadAlbumArtIntoView(localItem.id, itemHolder.albumArt); if (TimberUtils.isLollipop()) itemHolder.albumArt.setTransitionName("transition_album_art" + i); diff --git a/app/src/main/java/com/naman14/timber/adapters/ArtistSongAdapter.java b/app/src/main/java/com/naman14/timber/adapters/ArtistSongAdapter.java index 175f5523b..b80c64792 100644 --- a/app/src/main/java/com/naman14/timber/adapters/ArtistSongAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/ArtistSongAdapter.java @@ -17,9 +17,9 @@ import android.app.Activity; import android.graphics.Rect; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -41,7 +41,7 @@ import java.util.ArrayList; import java.util.List; -public class ArtistSongAdapter extends RecyclerView.Adapter { +public class ArtistSongAdapter extends BaseSongAdapter { private List arraylist; private Activity mContext; @@ -79,7 +79,9 @@ public void onBindViewHolder(ItemHolder itemHolder, int i) { itemHolder.title.setText(localItem.title); itemHolder.album.setText(localItem.albumName); - ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnFail(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); + ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), + itemHolder.albumArt, new DisplayImageOptions.Builder() + .cacheInMemory(true).showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); setOnPopupMenuListener(itemHolder, i - 1); } @@ -110,7 +112,7 @@ public void onClick(View v) { public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.popup_song_play: - MusicPlayer.playAll(mContext, songIDs, position, -1, TimberUtils.IdType.NA, false); + MusicPlayer.playAll(mContext, songIDs, position + 1, -1, TimberUtils.IdType.NA, false); break; case R.id.popup_song_play_next: long[] ids = new long[1]; @@ -131,6 +133,13 @@ public boolean onMenuItemClick(MenuItem item) { case R.id.popup_song_addto_playlist: AddPlaylistDialog.newInstance(arraylist.get(position + 1)).show(((AppCompatActivity) mContext).getSupportFragmentManager(), "ADD_PLAYLIST"); break; + case R.id.popup_song_share: + TimberUtils.shareTrack(mContext, arraylist.get(position + 1).id); + break; + case R.id.popup_song_delete: + long[] deleteIds = {arraylist.get(position + 1).id}; + TimberUtils.showDeleteDialog(mContext,arraylist.get(position + 1).title, deleteIds, ArtistSongAdapter.this, position + 1); + break; } return false; } @@ -165,7 +174,7 @@ private void clearExtraSpacingBetweenCards(RecyclerView albumsRecyclerview) { public long[] getSongIds() { List actualArraylist = new ArrayList(arraylist); - actualArraylist.remove(0); + //actualArraylist.remove(0); long[] ret = new long[actualArraylist.size()]; for (int i = 0; i < actualArraylist.size(); i++) { ret[i] = actualArraylist.get(i).id; @@ -173,6 +182,18 @@ public long[] getSongIds() { return ret; } + @Override + public void removeSongAt(int i){ + arraylist.remove(i); + updateDataSet(arraylist); + } + + @Override + public void updateDataSet(List arraylist) { + this.arraylist = arraylist; + this.songIDs = getSongIds(); + } + @Override public int getItemViewType(int position) { int viewType; @@ -207,8 +228,9 @@ public void onClick(View v) { handler.postDelayed(new Runnable() { @Override public void run() { - MusicPlayer.playAll(mContext, songIDs, getAdapterPosition() - 1, artistID, TimberUtils.IdType.Artist, false); - NavigationUtils.navigateToNowplaying(mContext, true); + playAll(mContext, songIDs, getAdapterPosition(), artistID, + TimberUtils.IdType.Artist, false, + arraylist.get(getAdapterPosition()), true); } }, 100); diff --git a/app/src/main/java/com/naman14/timber/adapters/BaseQueueAdapter.java b/app/src/main/java/com/naman14/timber/adapters/BaseQueueAdapter.java index 5cedd6ef7..9048ea650 100644 --- a/app/src/main/java/com/naman14/timber/adapters/BaseQueueAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/BaseQueueAdapter.java @@ -15,8 +15,8 @@ package com.naman14.timber.adapters; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -72,12 +72,16 @@ public void onBindViewHolder(ItemHolder itemHolder, int i) { if (MusicPlayer.isPlaying()) { itemHolder.visualizer.setColor(Config.accentColor(mContext, ateKey)); itemHolder.visualizer.setVisibility(View.VISIBLE); + } else { + itemHolder.visualizer.setVisibility(View.GONE); } } else { itemHolder.title.setTextColor(Config.textColorPrimary(mContext, ateKey)); itemHolder.visualizer.setVisibility(View.GONE); } - ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnFail(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); + ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), + itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true) + .showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); setOnPopupMenuListener(itemHolder, i); } @@ -119,6 +123,13 @@ public boolean onMenuItemClick(MenuItem item) { case R.id.popup_song_addto_playlist: AddPlaylistDialog.newInstance(arraylist.get(position)).show(mContext.getSupportFragmentManager(), "ADD_PLAYLIST"); break; + case R.id.popup_song_share: + TimberUtils.shareTrack(mContext, arraylist.get(position).id); + break; + case R.id.popup_song_delete: + long[] deleteIds = {arraylist.get(position).id}; + TimberUtils.showDeleteDialog(mContext,arraylist.get(position).title, deleteIds, BaseQueueAdapter.this, position); + break; } return false; } @@ -138,6 +149,10 @@ public long[] getSongIds() { return ret; } + public void removeSongAt(int i){ + arraylist.remove(i); + } + public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { protected TextView title, artist; protected ImageView albumArt, popupMenu; diff --git a/app/src/main/java/com/naman14/timber/adapters/BaseSongAdapter.java b/app/src/main/java/com/naman14/timber/adapters/BaseSongAdapter.java new file mode 100644 index 000000000..886bd1593 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/adapters/BaseSongAdapter.java @@ -0,0 +1,79 @@ +package com.naman14.timber.adapters; + +import android.app.Activity; + +import androidx.recyclerview.widget.RecyclerView; + +import android.view.View; +import android.view.ViewGroup; + +import com.google.android.gms.cast.framework.CastSession; +import com.naman14.timber.MusicPlayer; +import com.naman14.timber.activities.BaseActivity; +import com.naman14.timber.cast.TimberCastHelper; +import com.naman14.timber.models.Song; +import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.TimberUtils; + +import java.util.List; + +/** + * Created by naman on 7/12/17. + */ + +public class BaseSongAdapter extends RecyclerView.Adapter { + + @Override + public V onCreateViewHolder(ViewGroup parent, int viewType) { + return null; + } + + @Override + public void onBindViewHolder(V holder, int position) { + + } + + @Override + public int getItemCount() { + return 0; + } + + @Override + public int getItemViewType(int position) { + return super.getItemViewType(position); + } + + public class ItemHolder extends RecyclerView.ViewHolder { + + public ItemHolder(View view) { + super(view); + } + + } + + public void playAll(final Activity context, final long[] list, int position, + final long sourceId, final TimberUtils.IdType sourceType, + final boolean forceShuffle, final Song currentSong, boolean navigateNowPlaying) { + + if (context instanceof BaseActivity) { + CastSession castSession = ((BaseActivity) context).getCastSession(); + if (castSession != null) { + navigateNowPlaying = false; + TimberCastHelper.startCasting(castSession, currentSong); + } else { + MusicPlayer.playAll(context, list, position, -1, TimberUtils.IdType.NA, false); + } + } else { + MusicPlayer.playAll(context, list, position, -1, TimberUtils.IdType.NA, false); + } + + if (navigateNowPlaying) { + NavigationUtils.navigateToNowplaying(context, true); + } + + + } + public void removeSongAt(int i){} + public void updateDataSet(List arraylist) {} + +} diff --git a/app/src/main/java/com/naman14/timber/adapters/FolderAdapter.java b/app/src/main/java/com/naman14/timber/adapters/FolderAdapter.java new file mode 100644 index 000000000..4195347b4 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/adapters/FolderAdapter.java @@ -0,0 +1,259 @@ +package com.naman14.timber.adapters; + +import android.app.Activity; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Handler; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.naman14.timber.R; +import com.naman14.timber.dataloaders.FolderLoader; +import com.naman14.timber.dataloaders.SongLoader; +import com.naman14.timber.models.Song; +import com.naman14.timber.utils.PreferencesUtility; +import com.naman14.timber.utils.TimberUtils; +import com.naman14.timber.widgets.BubbleTextGetter; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by nv95 on 10.11.16. + */ + +public class FolderAdapter extends BaseSongAdapter implements BubbleTextGetter { + + @NonNull + private List mFileSet; + private List mSongs; + private File mRoot; + private Activity mContext; + private final Drawable[] mIcons; + private boolean mBusy = false; + + + public FolderAdapter(Activity context, File root) { + mContext = context; + mIcons = new Drawable[]{ + ContextCompat.getDrawable(context, R.drawable.ic_folder_open_black_24dp), + ContextCompat.getDrawable(context, R.drawable.ic_folder_parent_dark), + ContextCompat.getDrawable(context, R.drawable.ic_file_music_dark), + ContextCompat.getDrawable(context, R.drawable.ic_timer_wait) + }; + mSongs = new ArrayList<>(); + updateDataSet(root); + } + + public void applyTheme(boolean dark) { + ColorFilter cf = new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP); + for (Drawable d : mIcons) { + if (dark) { + d.setColorFilter(cf); + } else { + d.clearColorFilter(); + } + } + } + + @Override + public FolderAdapter.ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_folder_list, viewGroup, false); + return new ItemHolder(v); + } + + @Override + public void onBindViewHolder(final FolderAdapter.ItemHolder itemHolder, int i) { + File localItem = mFileSet.get(i); + Song song = mSongs.get(i); + itemHolder.title.setText(localItem.getName()); + if (localItem.isDirectory()) { + itemHolder.albumArt.setImageDrawable("..".equals(localItem.getName()) ? mIcons[1] : mIcons[0]); + } else { + ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(song.albumId).toString(), + itemHolder.albumArt, + new DisplayImageOptions.Builder(). + cacheInMemory(true).showImageOnFail(mIcons[2]) + .resetViewBeforeLoading(true).build()); + } + } + + @Override + public int getItemCount() { + return mFileSet.size(); + } + + @Deprecated + public void updateDataSet(File newRoot) { + if (mBusy) { + return; + } + if ("..".equals(newRoot.getName())) { + goUp(); + return; + } + mRoot = newRoot; + mFileSet = FolderLoader.getMediaFiles(newRoot, true); + getSongsForFiles(mFileSet); + } + + @Deprecated + public boolean goUp() { + if (mRoot == null || mBusy) { + return false; + } + File parent = mRoot.getParentFile(); + if (parent != null && parent.canRead()) { + updateDataSet(parent); + return true; + } else { + return false; + } + } + + public boolean goUpAsync() { + if (mRoot == null || mBusy) { + return false; + } + File parent = mRoot.getParentFile(); + if (parent != null && parent.canRead()) { + return updateDataSetAsync(parent); + } else { + return false; + } + } + + public boolean updateDataSetAsync(File newRoot) { + if (mBusy) { + return false; + } + if ("..".equals(newRoot.getName())) { + goUpAsync(); + return false; + } + mRoot = newRoot; + new NavigateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mRoot); + return true; + } + + @Override + public String getTextToShowInBubble(int pos) { + if (mBusy || mFileSet.size() == 0) + return ""; + try { + File f = mFileSet.get(pos); + if (f.isDirectory()) { + return String.valueOf(f.getName().charAt(0)); + } else { + return Character.toString(f.getName().charAt(0)); + } + } catch (Exception e) { + return ""; + } + } + + private void getSongsForFiles(List files) { + mSongs.clear(); + for (File file : files) { + mSongs.add(SongLoader.getSongFromPath(file.getAbsolutePath(), mContext)); + } + } + + + private class NavigateTask extends AsyncTask> { + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mBusy = true; + } + + @Override + protected List doInBackground(File... params) { + List files = FolderLoader.getMediaFiles(params[0], true); + getSongsForFiles(files); + return files; + } + + @Override + protected void onPostExecute(List files) { + super.onPostExecute(files); + mFileSet = files; + notifyDataSetChanged(); + mBusy = false; + PreferencesUtility.getInstance(mContext).storeLastFolder(mRoot.getPath()); + } + } + + public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + protected TextView title; + protected ImageView albumArt; + + public ItemHolder(View view) { + super(view); + this.title = (TextView) view.findViewById(R.id.folder_title); + this.albumArt = (ImageView) view.findViewById(R.id.album_art); + view.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + if (mBusy) { + return; + } + final File f = mFileSet.get(getAdapterPosition()); + + if (f.isDirectory() && updateDataSetAsync(f)) { + albumArt.setImageDrawable(mIcons[3]); + } else if (f.isFile()) { + + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + int current = -1; + long songId = SongLoader.getSongFromPath(mFileSet.get(getAdapterPosition()).getAbsolutePath(),mContext).id; + int count = 0; + for (Song song : mSongs) { + if (song.id != -1) { + count++; + } + } + long[] ret = new long[count]; + int j = 0; + for (int i = 0; i < getItemCount(); i++) { + if (mSongs.get(i).id != -1) { + ret[j] = mSongs.get(i).id; + if (mSongs.get(i).id == songId) { + current = j; + } + j++; + } + } + playAll(mContext, ret, current, -1, TimberUtils.IdType.NA, + false, mSongs.get(getAdapterPosition()), false); + } + }, 100); + + + } + } + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/naman14/timber/adapters/PlayingQueueAdapter.java b/app/src/main/java/com/naman14/timber/adapters/PlayingQueueAdapter.java index 0ffedacc1..9debfd043 100644 --- a/app/src/main/java/com/naman14/timber/adapters/PlayingQueueAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/PlayingQueueAdapter.java @@ -16,8 +16,9 @@ import android.app.Activity; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -41,6 +42,7 @@ import java.util.List; public class PlayingQueueAdapter extends RecyclerView.Adapter { + private static final String TAG = "PlayingQueueAdapter"; public int currentlyPlayingPosition; private List arraylist; @@ -73,12 +75,16 @@ public void onBindViewHolder(ItemHolder itemHolder, int i) { if (MusicPlayer.isPlaying()) { itemHolder.visualizer.setColor(Config.accentColor(mContext, ateKey)); itemHolder.visualizer.setVisibility(View.VISIBLE); + } else { + itemHolder.visualizer.setVisibility(View.GONE); } } else { itemHolder.title.setTextColor(Config.textColorPrimary(mContext, ateKey)); itemHolder.visualizer.setVisibility(View.GONE); } - ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnFail(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); + ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), + itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true) + .showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); setOnPopupMenuListener(itemHolder, i); } @@ -93,6 +99,12 @@ public void onClick(View v) { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { + case R.id.popup_song_remove_queue: + Log.v(TAG,"Removing " + position); + MusicPlayer.removeTrackAtPosition(getSongAt(position).id, position); + removeSongAt(position); + notifyItemRemoved(position); + break; case R.id.popup_song_play: MusicPlayer.playAll(mContext, getSongIds(), position, -1, TimberUtils.IdType.NA, false); break; diff --git a/app/src/main/java/com/naman14/timber/adapters/PlaylistAdapter.java b/app/src/main/java/com/naman14/timber/adapters/PlaylistAdapter.java new file mode 100644 index 000000000..9a094f14b --- /dev/null +++ b/app/src/main/java/com/naman14/timber/adapters/PlaylistAdapter.java @@ -0,0 +1,263 @@ +package com.naman14.timber.adapters; + +import android.app.Activity; +import android.graphics.Bitmap; +import androidx.palette.graphics.Palette; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.afollestad.appthemeengine.Config; +import com.naman14.timber.R; +import com.naman14.timber.dataloaders.LastAddedLoader; +import com.naman14.timber.dataloaders.PlaylistSongLoader; +import com.naman14.timber.dataloaders.SongLoader; +import com.naman14.timber.dataloaders.TopTracksLoader; +import com.naman14.timber.models.Playlist; +import com.naman14.timber.models.Song; +import com.naman14.timber.utils.Constants; +import com.naman14.timber.utils.Helpers; +import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.PreferencesUtility; +import com.naman14.timber.utils.TimberUtils; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; + +import java.util.List; +import java.util.Random; + +/** + * Created by naman on 31/10/16. + */ +public class PlaylistAdapter extends RecyclerView.Adapter { + + private List arraylist; + private Activity mContext; + private boolean isGrid; + private boolean showAuto; + private int songCountInt; + private long totalRuntime; + private long firstAlbumID = -1; + private int foregroundColor; + int[] foregroundColors = {R.color.pink_transparent, R.color.green_transparent, R.color.blue_transparent, R.color.red_transparent, R.color.purple_transparent}; + + public PlaylistAdapter(Activity context, List arraylist) { + this.arraylist = arraylist; + this.mContext = context; + this.isGrid = PreferencesUtility.getInstance(mContext).getPlaylistView() == Constants.PLAYLIST_VIEW_GRID; + this.showAuto = PreferencesUtility.getInstance(mContext).showAutoPlaylist(); + Random random = new Random(); + int rndInt = random.nextInt(foregroundColors.length); + foregroundColor = foregroundColors[rndInt]; + + } + + @Override + public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + if (isGrid) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_grid, null); + ItemHolder ml = new ItemHolder(v); + return ml; + } else { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_album_list, null); + ItemHolder ml = new ItemHolder(v); + return ml; + } + } + + @Override + public void onBindViewHolder(final ItemHolder itemHolder, int i) { + final Playlist localItem = arraylist.get(i); + + itemHolder.title.setText(localItem.name); + + String s = getAlbumArtUri(i, localItem.id); + itemHolder.albumArt.setTag(firstAlbumID); + ImageLoader.getInstance().displayImage(s, itemHolder.albumArt, + new DisplayImageOptions.Builder().cacheInMemory(true) + .showImageOnFail(R.drawable.ic_empty_music2) + .resetViewBeforeLoading(true) + .build(), new SimpleImageLoadingListener() { + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + if (isGrid) { + new Palette.Builder(loadedImage).generate(new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + Palette.Swatch swatch = palette.getVibrantSwatch(); + if (swatch != null) { + int color = swatch.getRgb(); + itemHolder.footer.setBackgroundColor(color); + int textColor = TimberUtils.getBlackWhiteColor(swatch.getTitleTextColor()); + itemHolder.title.setTextColor(textColor); + itemHolder.artist.setTextColor(textColor); + } else { + Palette.Swatch mutedSwatch = palette.getMutedSwatch(); + if (mutedSwatch != null) { + int color = mutedSwatch.getRgb(); + itemHolder.footer.setBackgroundColor(color); + int textColor = TimberUtils.getBlackWhiteColor(mutedSwatch.getTitleTextColor()); + itemHolder.title.setTextColor(textColor); + itemHolder.artist.setTextColor(textColor); + } + } + + + } + }); + } + + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + if (isGrid) { + itemHolder.footer.setBackgroundColor(0); + if (mContext != null) { + int textColorPrimary = Config.textColorPrimary(mContext, Helpers.getATEKey(mContext)); + itemHolder.title.setTextColor(textColorPrimary); + itemHolder.artist.setTextColor(textColorPrimary); + } + } + } + }); + itemHolder.artist.setText(" " + String.valueOf(songCountInt) + " " + mContext.getString(R.string.songs) + " - " + TimberUtils.makeShortTimeString(mContext,totalRuntime)); + + if (TimberUtils.isLollipop()) + itemHolder.albumArt.setTransitionName("transition_album_art" + i); + + } + + private String getAlbumArtUri(int position, long id) { + if (mContext != null) { + firstAlbumID = -1; + if (showAuto) { + switch (position) { + case 0: + List lastAddedSongs = LastAddedLoader.getLastAddedSongs(mContext); + songCountInt = lastAddedSongs.size(); + totalRuntime = 0; + for(Song song : lastAddedSongs){ + totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be + } + + if (songCountInt != 0) { + firstAlbumID = lastAddedSongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + case 1: + TopTracksLoader recentloader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.RecentSongs); + List recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor()); + songCountInt = recentsongs.size(); + totalRuntime = 0; + for(Song song : recentsongs){ + totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be + } + + if (songCountInt != 0) { + firstAlbumID = recentsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + case 2: + TopTracksLoader topTracksLoader = new TopTracksLoader(mContext, TopTracksLoader.QueryType.TopTracks); + List topsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor()); + songCountInt = topsongs.size(); + totalRuntime = 0; + for(Song song : topsongs){ + totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be + } + + if (songCountInt != 0) { + firstAlbumID = topsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + default: + List playlistsongs = PlaylistSongLoader.getSongsInPlaylist(mContext, id); + songCountInt = playlistsongs.size(); + totalRuntime = 0; + for(Song song : playlistsongs){ + totalRuntime += song.duration; + } + + if (songCountInt != 0) { + firstAlbumID = playlistsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + + } + } else { + List playlistsongs = PlaylistSongLoader.getSongsInPlaylist(mContext, id); + songCountInt = playlistsongs.size(); + totalRuntime = 0; + for(Song song : playlistsongs){ + totalRuntime += song.duration; + } + + if (songCountInt != 0) { + firstAlbumID = playlistsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + } + } + return null; + } + + @Override + public int getItemCount() { + return (null != arraylist ? arraylist.size() : 0); + } + + public void updateDataSet(List arraylist) { + this.arraylist.clear(); + this.arraylist.addAll(arraylist); + notifyDataSetChanged(); + } + + public class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + protected TextView title, artist; + protected ImageView albumArt; + protected View footer; + + public ItemHolder(View view) { + super(view); + this.title = (TextView) view.findViewById(R.id.album_title); + this.artist = (TextView) view.findViewById(R.id.album_artist); + this.albumArt = (ImageView) view.findViewById(R.id.album_art); + this.footer = view.findViewById(R.id.footer); + view.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + NavigationUtils.navigateToPlaylistDetail(mContext, getPlaylistType(getAdapterPosition()), (long) albumArt.getTag(), String.valueOf(title.getText()), foregroundColor, arraylist.get(getAdapterPosition()).id, null); + + } + + } + + private String getPlaylistType(int position) { + if (showAuto) { + switch (position) { + case 0: + return Constants.NAVIGATE_PLAYLIST_LASTADDED; + case 1: + return Constants.NAVIGATE_PLAYLIST_RECENT; + case 2: + return Constants.NAVIGATE_PLAYLIST_TOPTRACKS; + default: + return Constants.NAVIGATE_PLAYLIST_USERCREATED; + } + } else return Constants.NAVIGATE_PLAYLIST_USERCREATED; + } + + +} + + + + diff --git a/app/src/main/java/com/naman14/timber/adapters/SearchAdapter.java b/app/src/main/java/com/naman14/timber/adapters/SearchAdapter.java index a5460733d..b46a33edf 100644 --- a/app/src/main/java/com/naman14/timber/adapters/SearchAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/SearchAdapter.java @@ -16,8 +16,8 @@ import android.app.Activity; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -45,7 +45,7 @@ import java.util.Collections; import java.util.List; -public class SearchAdapter extends RecyclerView.Adapter { +public class SearchAdapter extends BaseSongAdapter { private Activity mContext; private List searchResults = Collections.emptyList(); @@ -188,6 +188,9 @@ public boolean onMenuItemClick(MenuItem item) { } }); menu.inflate(R.menu.popup_song); + //Hide these because they aren't implemented + menu.getMenu().findItem(R.id.popup_song_delete).setVisible(false); + menu.getMenu().findItem(R.id.popup_song_share).setVisible(false); menu.show(); } }); @@ -243,7 +246,8 @@ public void onClick(View v) { public void run() { long[] ret = new long[1]; ret[0] = ((Song) searchResults.get(getAdapterPosition())).id; - MusicPlayer.playAll(mContext, ret, 0, -1, TimberUtils.IdType.NA, false); + playAll(mContext, ret, 0, -1, TimberUtils.IdType.NA, + false, (Song) searchResults.get(getAdapterPosition()), false); } }, 100); diff --git a/app/src/main/java/com/naman14/timber/adapters/Timber4QueueAdapter.java b/app/src/main/java/com/naman14/timber/adapters/SlidingQueueAdapter.java similarity index 88% rename from app/src/main/java/com/naman14/timber/adapters/Timber4QueueAdapter.java rename to app/src/main/java/com/naman14/timber/adapters/SlidingQueueAdapter.java index 585441c7a..b2290b7ac 100644 --- a/app/src/main/java/com/naman14/timber/adapters/Timber4QueueAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/SlidingQueueAdapter.java @@ -16,7 +16,7 @@ import android.app.Activity; import android.os.Handler; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,14 +33,14 @@ import java.util.List; -public class Timber4QueueAdapter extends RecyclerView.Adapter { +public class SlidingQueueAdapter extends RecyclerView.Adapter { public static int currentlyPlayingPosition; private List arraylist; private Activity mContext; private int lastPosition = -1; - public Timber4QueueAdapter(Activity context, List arraylist) { + public SlidingQueueAdapter(Activity context, List arraylist) { this.arraylist = arraylist; this.mContext = context; currentlyPlayingPosition = MusicPlayer.getQueuePosition(); @@ -48,7 +48,7 @@ public Timber4QueueAdapter(Activity context, List arraylist) { @Override public ItemHolder onCreateViewHolder(ViewGroup viewGroup, int i) { - View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_queue_timber4_bottom, null); + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_song_sliding_queue, null); ItemHolder ml = new ItemHolder(v); return ml; } @@ -59,7 +59,9 @@ public void onBindViewHolder(ItemHolder itemHolder, int i) { // setAnimation(itemHolder.itemView, i); Song localItem = arraylist.get(i); - ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnFail(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); + ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), + itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true) + .showImageOnLoading(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); } diff --git a/app/src/main/java/com/naman14/timber/adapters/SongsListAdapter.java b/app/src/main/java/com/naman14/timber/adapters/SongsListAdapter.java index b4a597761..8e3ac1ff4 100644 --- a/app/src/main/java/com/naman14/timber/adapters/SongsListAdapter.java +++ b/app/src/main/java/com/naman14/timber/adapters/SongsListAdapter.java @@ -16,8 +16,9 @@ import android.graphics.Color; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.RecyclerView; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.RecyclerView; + import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -35,7 +36,6 @@ import com.naman14.timber.models.Song; import com.naman14.timber.utils.Helpers; import com.naman14.timber.utils.NavigationUtils; -import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.TimberUtils; import com.naman14.timber.widgets.BubbleTextGetter; import com.naman14.timber.widgets.MusicVisualizer; @@ -44,22 +44,25 @@ import java.util.List; -public class SongsListAdapter extends RecyclerView.Adapter implements BubbleTextGetter { +public class SongsListAdapter extends BaseSongAdapter implements BubbleTextGetter { public int currentlyPlayingPosition; private List arraylist; private AppCompatActivity mContext; private long[] songIDs; private boolean isPlaylist; + private boolean animate; private int lastPosition = -1; private String ateKey; + private long playlistId; - public SongsListAdapter(AppCompatActivity context, List arraylist, boolean isPlaylistSong) { + public SongsListAdapter(AppCompatActivity context, List arraylist, boolean isPlaylistSong, boolean animate) { this.arraylist = arraylist; this.mContext = context; this.isPlaylist = isPlaylistSong; this.songIDs = getSongIds(); this.ateKey = Helpers.getATEKey(context); + this.animate = animate; } @Override @@ -82,23 +85,30 @@ public void onBindViewHolder(ItemHolder itemHolder, int i) { itemHolder.title.setText(localItem.title); itemHolder.artist.setText(localItem.artistName); - ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnFail(R.drawable.ic_empty_music2).resetViewBeforeLoading(true).build()); + ImageLoader.getInstance().displayImage(TimberUtils.getAlbumArtUri(localItem.albumId).toString(), + itemHolder.albumArt, new DisplayImageOptions.Builder().cacheInMemory(true) + .showImageOnLoading(R.drawable.ic_empty_music2) + .resetViewBeforeLoading(true).build()); + if (MusicPlayer.getCurrentAudioId() == localItem.id) { itemHolder.title.setTextColor(Config.accentColor(mContext, ateKey)); if (MusicPlayer.isPlaying()) { itemHolder.visualizer.setColor(Config.accentColor(mContext, ateKey)); itemHolder.visualizer.setVisibility(View.VISIBLE); + } else { + itemHolder.visualizer.setVisibility(View.GONE); } } else { - if (isPlaylist) + itemHolder.visualizer.setVisibility(View.GONE); + if (isPlaylist) { itemHolder.title.setTextColor(Color.WHITE); - else + } else { itemHolder.title.setTextColor(Config.textColorPrimary(mContext, ateKey)); - itemHolder.visualizer.setVisibility(View.GONE); + } } - if (isPlaylist && PreferencesUtility.getInstance(mContext).getAnimations()) { + if (animate && isPlaylist) { if (TimberUtils.isLollipop()) setAnimation(itemHolder.itemView, i); else { @@ -106,10 +116,16 @@ public void onBindViewHolder(ItemHolder itemHolder, int i) { setAnimation(itemHolder.itemView, i); } } + + setOnPopupMenuListener(itemHolder, i); } + public void setPlaylistId(long playlistId) { + this.playlistId = playlistId; + } + @Override public int getItemCount() { return (null != arraylist ? arraylist.size() : 0); @@ -122,10 +138,16 @@ private void setOnPopupMenuListener(ItemHolder itemHolder, final int position) { public void onClick(View v) { final PopupMenu menu = new PopupMenu(mContext, v); + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { + case R.id.popup_song_remove_playlist: + TimberUtils.removeFromPlaylist(mContext, arraylist.get(position).id, playlistId); + removeSongAt(position); + notifyItemRemoved(position); + break; case R.id.popup_song_play: MusicPlayer.playAll(mContext, songIDs, position, -1, TimberUtils.IdType.NA, false); break; @@ -148,12 +170,21 @@ public boolean onMenuItemClick(MenuItem item) { case R.id.popup_song_addto_playlist: AddPlaylistDialog.newInstance(arraylist.get(position)).show(mContext.getSupportFragmentManager(), "ADD_PLAYLIST"); break; + case R.id.popup_song_share: + TimberUtils.shareTrack(mContext, arraylist.get(position).id); + break; + case R.id.popup_song_delete: + long[] deleteIds = {arraylist.get(position).id}; + TimberUtils.showDeleteDialog(mContext,arraylist.get(position).title, deleteIds, SongsListAdapter.this, position); + break; } return false; } }); menu.inflate(R.menu.popup_song); menu.show(); + if (isPlaylist) + menu.getMenu().findItem(R.id.popup_song_remove_playlist).setVisible(true); } }); } @@ -187,6 +218,7 @@ private void setAnimation(View viewToAnimate, int position) { } } + @Override public void updateDataSet(List arraylist) { this.arraylist = arraylist; this.songIDs = getSongIds(); @@ -213,7 +245,9 @@ public void onClick(View v) { handler.postDelayed(new Runnable() { @Override public void run() { - MusicPlayer.playAll(mContext, songIDs, getAdapterPosition(), -1, TimberUtils.IdType.NA, false); + playAll(mContext, songIDs, getAdapterPosition(), -1, + TimberUtils.IdType.NA, false, + arraylist.get(getAdapterPosition()), false); Handler handler1 = new Handler(); handler1.postDelayed(new Runnable() { @Override @@ -229,6 +263,20 @@ public void run() { } } + + public Song getSongAt(int i) { + return arraylist.get(i); + } + + public void addSongTo(int i, Song song) { + arraylist.add(i, song); + } + + @Override + public void removeSongAt(int i) { + arraylist.remove(i); + updateDataSet(arraylist); + } } diff --git a/app/src/main/java/com/naman14/timber/cast/CastOptionsProvider.java b/app/src/main/java/com/naman14/timber/cast/CastOptionsProvider.java new file mode 100644 index 000000000..06f2c3a21 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/cast/CastOptionsProvider.java @@ -0,0 +1,47 @@ +package com.naman14.timber.cast; + +import android.content.Context; + +import com.google.android.gms.cast.framework.CastOptions; +import com.google.android.gms.cast.framework.OptionsProvider; +import com.google.android.gms.cast.framework.SessionProvider; +import com.google.android.gms.cast.framework.media.CastMediaOptions; +import com.google.android.gms.cast.framework.media.MediaIntentReceiver; +import com.google.android.gms.cast.framework.media.NotificationOptions; +import com.naman14.timber.R; + +import java.util.ArrayList; +import java.util.List; + +public class CastOptionsProvider implements OptionsProvider { + + @Override + public CastOptions getCastOptions(Context appContext) { + + List buttonActions = new ArrayList<>(); + buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK); + buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING); + int[] compatButtonActionsIndicies = new int[]{ 0, 1 }; + + NotificationOptions notificationOptions = new NotificationOptions.Builder() + .setActions(buttonActions, compatButtonActionsIndicies) + .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) + .build(); + + CastMediaOptions mediaOptions = new CastMediaOptions.Builder() + .setNotificationOptions(notificationOptions) + .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) + .build(); + + CastOptions castOptions = new CastOptions.Builder() + .setReceiverApplicationId(appContext.getString(R.string.cast_app_id)) + .setCastMediaOptions(mediaOptions) + .build(); + + return castOptions; + } + @Override + public List getAdditionalSessionProviders(Context context) { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/naman14/timber/cast/ExpandedControlsActivity.java b/app/src/main/java/com/naman14/timber/cast/ExpandedControlsActivity.java new file mode 100644 index 000000000..c96c47d76 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/cast/ExpandedControlsActivity.java @@ -0,0 +1,28 @@ +package com.naman14.timber.cast; + +import android.os.Bundle; +import android.view.Menu; +import android.view.View; +import android.view.WindowManager; + +import com.google.android.gms.cast.framework.CastButtonFactory; +import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity; +import com.naman14.timber.R; + +public class ExpandedControlsActivity extends ExpandedControllerActivity { + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.menu_expanded_controller, menu); + CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); + return true; + } + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/naman14/timber/cast/SimpleSessionManagerListener.java b/app/src/main/java/com/naman14/timber/cast/SimpleSessionManagerListener.java new file mode 100644 index 000000000..0610e4113 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/cast/SimpleSessionManagerListener.java @@ -0,0 +1,44 @@ +package com.naman14.timber.cast; + +import com.google.android.gms.cast.framework.Session; +import com.google.android.gms.cast.framework.SessionManagerListener; + +/** + * Created by naman on 7/12/17. + */ + +public class SimpleSessionManagerListener implements SessionManagerListener { + + public void onSessionStarted(Session session, String sessionId) { + } + + public void onSessionResumed(Session session, boolean wasSuspended) { + } + + public void onSessionEnded(Session session, int error) { + } + + public void onSessionSuspended(Session session, int i) { + + } + + public void onSessionStarting(Session session) { + + } + + public void onSessionEnding(Session session) { + + } + + public void onSessionResuming(Session session, String s) { + + } + + public void onSessionResumeFailed(Session session, int i) { + + } + + public void onSessionStartFailed(Session session, int i) { + + } +} diff --git a/app/src/main/java/com/naman14/timber/cast/TimberCastHelper.java b/app/src/main/java/com/naman14/timber/cast/TimberCastHelper.java new file mode 100644 index 000000000..44bd38131 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/cast/TimberCastHelper.java @@ -0,0 +1,60 @@ +package com.naman14.timber.cast; + +import android.net.Uri; +import android.util.Log; + +import com.google.android.gms.cast.MediaInfo; +import com.google.android.gms.cast.MediaMetadata; +import com.google.android.gms.cast.TextTrackStyle; +import com.google.android.gms.cast.framework.CastSession; +import com.google.android.gms.cast.framework.media.RemoteMediaClient; +import com.google.android.gms.common.images.WebImage; +import com.naman14.timber.models.Song; +import com.naman14.timber.utils.Constants; +import com.naman14.timber.utils.TimberUtils; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Created by naman on 2/12/17. + */ + +public class TimberCastHelper { + + public static void startCasting(CastSession castSession, Song song) { + + String ipAddress = TimberUtils.getIPAddress(true); + URL baseUrl; + try { + baseUrl = new URL("http", ipAddress, Constants.CAST_SERVER_PORT, "" ); + } catch (MalformedURLException e) { + e.printStackTrace(); + return; + } + + String songUrl = baseUrl.toString() + "/song?id=" + song.id; + String albumArtUrl = baseUrl.toString() + "/albumart?id=" + song.albumId; + + MediaMetadata musicMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK); + + musicMetadata.putString(MediaMetadata.KEY_TITLE, song.title); + musicMetadata.putString(MediaMetadata.KEY_ARTIST, song.artistName); + musicMetadata.putString(MediaMetadata.KEY_ALBUM_TITLE, song.albumName); + musicMetadata.putInt(MediaMetadata.KEY_TRACK_NUMBER, song.trackNumber); + musicMetadata.addImage(new WebImage(Uri.parse(albumArtUrl))); + + try { + MediaInfo mediaInfo = new MediaInfo.Builder(songUrl) + .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) + .setContentType("audio/mpeg") + .setMetadata(musicMetadata) + .setStreamDuration(song.duration) + .build(); + RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient(); + remoteMediaClient.load(mediaInfo, true, 0); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/com/naman14/timber/cast/WebServer.java b/app/src/main/java/com/naman14/timber/cast/WebServer.java new file mode 100644 index 000000000..0c3d0a593 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/cast/WebServer.java @@ -0,0 +1,76 @@ +package com.naman14.timber.cast; + +import android.content.Context; +import android.net.Uri; + +import com.naman14.timber.utils.Constants; +import com.naman14.timber.utils.TimberUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Map; + +import fi.iki.elonen.NanoHTTPD; + +public class WebServer extends NanoHTTPD { + + private Context context; + private Uri songUri, albumArtUri; + + public WebServer(Context context) { + super(Constants.CAST_SERVER_PORT); + this.context = context; + } + + @Override + public Response serve(String uri, Method method, + Map header, + Map parameters, + Map files) { + if (uri.contains("albumart")) { + //serve the picture + + String albumId = parameters.get("id"); + this.albumArtUri = TimberUtils.getAlbumArtUri(Long.parseLong(albumId)); + + if (albumArtUri != null) { + String mediasend = "image/jpg"; + InputStream fisAlbumArt = null; + try { + fisAlbumArt = context.getContentResolver().openInputStream(albumArtUri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + Response.Status st = Response.Status.OK; + + //serve the song + return newChunkedResponse(st, mediasend, fisAlbumArt); + } + + } else if (uri.contains("song")) { + + String songId = parameters.get("id"); + this.songUri = TimberUtils.getSongUri(context, Long.parseLong(songId)); + + if (songUri != null) { + String mediasend = "audio/mp3"; + FileInputStream fisSong = null; + File song = new File(songUri.getPath()); + try { + fisSong = new FileInputStream(song); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + Response.Status st = Response.Status.OK; + + //serve the song + return newFixedLengthResponse(st, mediasend, fisSong, song.length()); + } + + } + return newFixedLengthResponse("Error"); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/naman14/timber/dataloaders/AlbumLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/AlbumLoader.java index fc3dea7ee..82b1e77cd 100644 --- a/app/src/main/java/com/naman14/timber/dataloaders/AlbumLoader.java +++ b/app/src/main/java/com/naman14/timber/dataloaders/AlbumLoader.java @@ -59,8 +59,12 @@ public static Album getAlbum(Context context, long id) { return getAlbum(makeAlbumCursor(context, "_id=?", new String[]{String.valueOf(id)})); } - public static List getAlbums(Context context, String paramString) { - return getAlbumsForCursor(makeAlbumCursor(context, "album LIKE ?", new String[]{"%" + paramString + "%"})); + public static List getAlbums(Context context, String paramString, int limit) { + List result = getAlbumsForCursor(makeAlbumCursor(context, "album LIKE ?", new String[]{paramString + "%"})); + if (result.size() < limit) { + result.addAll(getAlbumsForCursor(makeAlbumCursor(context, "album LIKE ?", new String[]{"%_" + paramString + "%"}))); + } + return result.size() < limit ? result : result.subList(0, limit); } diff --git a/app/src/main/java/com/naman14/timber/dataloaders/ArtistAlbumLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/ArtistAlbumLoader.java index fb026b705..e62d61577 100644 --- a/app/src/main/java/com/naman14/timber/dataloaders/ArtistAlbumLoader.java +++ b/app/src/main/java/com/naman14/timber/dataloaders/ArtistAlbumLoader.java @@ -19,40 +19,26 @@ import android.provider.MediaStore; import com.naman14.timber.models.Album; +import com.naman14.timber.models.Song; import java.util.ArrayList; +import java.util.List; public class ArtistAlbumLoader { public static ArrayList getAlbumsForArtist(Context context, long artistID) { - ArrayList albumList = new ArrayList(); - Cursor cursor = makeAlbumForArtistCursor(context, artistID); - - if (cursor != null) { - if (cursor.moveToFirst()) - do { - - Album album = new Album(cursor.getLong(0), cursor.getString(1), cursor.getString(2), artistID, cursor.getInt(3), cursor.getInt(4)); - albumList.add(album); - } - while (cursor.moveToNext()); - - } - if (cursor != null) - cursor.close(); - return albumList; - } - - - public static Cursor makeAlbumForArtistCursor(Context context, long artistID) { - if (artistID == -1) return null; - Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Artists.Albums.getContentUri("external", artistID), new String[]{"_id", "album", "artist", "numsongs", "minyear"}, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER); - - return cursor; + List allAlbums = AlbumLoader.getAllAlbums(context); + ArrayList artistAlbums = new ArrayList<>(); + for (Album album: allAlbums) { + if (album.artistId == artistID) { + artistAlbums.add(album); + } + } + return artistAlbums; } } diff --git a/app/src/main/java/com/naman14/timber/dataloaders/ArtistLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/ArtistLoader.java index 6f765512d..875a71193 100644 --- a/app/src/main/java/com/naman14/timber/dataloaders/ArtistLoader.java +++ b/app/src/main/java/com/naman14/timber/dataloaders/ArtistLoader.java @@ -57,8 +57,12 @@ public static Artist getArtist(Context context, long id) { return getArtist(makeArtistCursor(context, "_id=?", new String[]{String.valueOf(id)})); } - public static List getArtists(Context context, String paramString) { - return getArtistsForCursor(makeArtistCursor(context, "artist LIKE ?", new String[]{"%" + paramString + "%"})); + public static List getArtists(Context context, String paramString, int limit) { + List result = getArtistsForCursor(makeArtistCursor(context, "artist LIKE ?", new String[]{paramString + "%"})); + if (result.size() < limit) { + result.addAll(getArtistsForCursor(makeArtistCursor(context, "artist LIKE ?", new String[]{"%_" + paramString + "%"}))); + } + return result.size() < limit ? result : result.subList(0, limit); } diff --git a/app/src/main/java/com/naman14/timber/dataloaders/FolderLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/FolderLoader.java new file mode 100644 index 000000000..6a81ce7a4 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/dataloaders/FolderLoader.java @@ -0,0 +1,103 @@ +package com.naman14.timber.dataloaders; + +import android.text.TextUtils; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Created by nv95 on 10.11.16. + */ + +public class FolderLoader { + + private static final String[] SUPPORTED_EXT = new String[] { + "mp3", + "mp4", + "m4a", + "aac", + "ogg", + "wav" + }; + + public static List getMediaFiles(File dir, final boolean acceptDirs) { + ArrayList list = new ArrayList<>(); + list.add(new File(dir, "..")); + if (dir.isDirectory()) { + List files = Arrays.asList(dir.listFiles(new FileFilter() { + + @Override + public boolean accept(File file) { + if (file.isFile()) { + String name = file.getName(); + return !".nomedia".equals(name) && checkFileExt(name); + } else if (file.isDirectory()) { + return acceptDirs && checkDir(file); + } else + return false; + } + })); + Collections.sort(files, new FilenameComparator()); + Collections.sort(files, new DirFirstComparator()); + list.addAll(files); + } + + return list; + } + + public static boolean isMediaFile(File file) { + return file.exists() && file.canRead() && checkFileExt(file.getName()); + } + + private static boolean checkDir(File dir) { + return dir.exists() && dir.canRead() && !".".equals(dir.getName()) && dir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + String name = pathname.getName(); + return !".".equals(name) && !"..".equals(name) && pathname.canRead() && (pathname.isDirectory() || (pathname.isFile() && checkFileExt(name))); + } + + }).length != 0; + } + + private static boolean checkFileExt(String name) { + if (TextUtils.isEmpty(name)) { + return false; + } + int p = name.lastIndexOf(".") + 1; + if (p < 1) { + return false; + } + String ext = name.substring(p).toLowerCase(); + for (String o : SUPPORTED_EXT) { + if (o.equals(ext)) { + return true; + } + } + return false; + } + + private static class FilenameComparator implements Comparator { + @Override + public int compare(File f1, File f2) { + return f1.getName().compareTo(f2.getName()); + } + } + + private static class DirFirstComparator implements Comparator { + @Override + public int compare(File f1, File f2) { + if (f1.isDirectory() == f2.isDirectory()) + return 0; + else if (f1.isDirectory() && !f2.isDirectory()) + return -1; + else + return 1; + } + } +} diff --git a/app/src/main/java/com/naman14/timber/dataloaders/LastAddedLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/LastAddedLoader.java index ffc80de12..07d4c1fc4 100644 --- a/app/src/main/java/com/naman14/timber/dataloaders/LastAddedLoader.java +++ b/app/src/main/java/com/naman14/timber/dataloaders/LastAddedLoader.java @@ -20,6 +20,7 @@ import android.provider.MediaStore.Audio.AudioColumns; import com.naman14.timber.models.Song; +import com.naman14.timber.utils.PreferencesUtility; import java.util.ArrayList; import java.util.List; @@ -59,7 +60,7 @@ public static List getLastAddedSongs(Context context) { public static final Cursor makeLastAddedCursor(final Context context) { //four weeks ago long fourWeeksAgo = (System.currentTimeMillis() / 1000) - (4 * 3600 * 24 * 7); - long cutoff = 0L; + long cutoff = PreferencesUtility.getInstance(context).getLastAddedCutoff(); // use the most recent of the two timestamps if (cutoff < fourWeeksAgo) { cutoff = fourWeeksAgo; diff --git a/app/src/main/java/com/naman14/timber/dataloaders/PlaylistLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/PlaylistLoader.java index 580809ca9..5a6151a4d 100644 --- a/app/src/main/java/com/naman14/timber/dataloaders/PlaylistLoader.java +++ b/app/src/main/java/com/naman14/timber/dataloaders/PlaylistLoader.java @@ -17,6 +17,7 @@ import android.content.Context; import android.content.res.Resources; import android.database.Cursor; +import android.net.Uri; import android.provider.BaseColumns; import android.provider.MediaStore; import android.provider.MediaStore.Audio.PlaylistsColumns; @@ -89,4 +90,13 @@ public static final Cursor makePlaylistCursor(final Context context) { PlaylistsColumns.NAME }, null, null, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER); } + + public static void deletePlaylists(Context context, long playlistId) { + Uri localUri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI; + StringBuilder localStringBuilder = new StringBuilder(); + localStringBuilder.append("_id IN ("); + localStringBuilder.append((playlistId)); + localStringBuilder.append(")"); + context.getContentResolver().delete(localUri, localStringBuilder.toString(), null); + } } diff --git a/app/src/main/java/com/naman14/timber/dataloaders/QueueLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/QueueLoader.java index 8de243600..601339f23 100644 --- a/app/src/main/java/com/naman14/timber/dataloaders/QueueLoader.java +++ b/app/src/main/java/com/naman14/timber/dataloaders/QueueLoader.java @@ -47,9 +47,9 @@ public static List getQueueSongs(Context context) { final int duration = mCursor.getInt(5); - final long artistid = mCursor.getInt(6); + final long artistid = mCursor.getInt(7); - final int tracknumber = mCursor.getInt(7); + final int tracknumber = mCursor.getInt(6); final Song song = new Song(id, albumId, artistid, songName, artist, album, duration, tracknumber); @@ -63,4 +63,6 @@ public static List getQueueSongs(Context context) { return mSongList; } + + } diff --git a/app/src/main/java/com/naman14/timber/dataloaders/SongLoader.java b/app/src/main/java/com/naman14/timber/dataloaders/SongLoader.java index f7bf68db7..8737d06a3 100644 --- a/app/src/main/java/com/naman14/timber/dataloaders/SongLoader.java +++ b/app/src/main/java/com/naman14/timber/dataloaders/SongLoader.java @@ -14,8 +14,11 @@ package com.naman14.timber.dataloaders; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; +import android.media.MediaMetadataRetriever; +import android.net.Uri; import android.provider.BaseColumns; import android.provider.MediaStore; import android.text.TextUtils; @@ -24,6 +27,7 @@ import com.naman14.timber.utils.PreferencesUtility; import java.util.ArrayList; +import java.util.List; public class SongLoader { @@ -92,29 +96,75 @@ public static final long[] getSongListForCursor(Cursor cursor) { return list; } + public static Song getSongFromPath(String songPath, Context context) { + ContentResolver cr = context.getContentResolver(); + + Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + String selection = MediaStore.Audio.Media.DATA; + String[] selectionArgs = {songPath}; + String[] projection = new String[]{"_id", "title", "artist", "album", "duration", "track", "artist_id", "album_id"}; + String sortOrder = MediaStore.Audio.Media.TITLE + " ASC"; + + Cursor cursor = cr.query(uri, projection, selection + "=?", selectionArgs, sortOrder); + + if (cursor != null && cursor.getCount() > 0) { + Song song = getSongForCursor(cursor); + cursor.close(); + return song; + } + else return new Song(); + } + public static ArrayList getAllSongs(Context context) { return getSongsForCursor(makeSongCursor(context, null, null)); } + public static long[] getSongListInFolder(Context context, String path) { + String[] whereArgs = new String[]{path + "%"}; + return getSongListForCursor(makeSongCursor(context, MediaStore.Audio.Media.DATA + " LIKE ?", whereArgs, null)); + } + public static Song getSongForID(Context context, long id) { return getSongForCursor(makeSongCursor(context, "_id=" + String.valueOf(id), null)); } - public static ArrayList searchSongs(Context context, String searchString) { - return getSongsForCursor(makeSongCursor(context, "title LIKE ?", new String[]{"%" + searchString + "%"})); + public static List searchSongs(Context context, String searchString, int limit) { + ArrayList result = getSongsForCursor(makeSongCursor(context, "title LIKE ?", new String[]{searchString + "%"})); + if (result.size() < limit) { + result.addAll(getSongsForCursor(makeSongCursor(context, "title LIKE ?", new String[]{"%_" + searchString + "%"}))); + } + return result.size() < limit ? result : result.subList(0, limit); } public static Cursor makeSongCursor(Context context, String selection, String[] paramArrayOfString) { - String selectionStatement = "is_music=1 AND title != ''"; final String songSortOrder = PreferencesUtility.getInstance(context).getSongSortOrder(); + return makeSongCursor(context, selection, paramArrayOfString, songSortOrder); + } + + private static Cursor makeSongCursor(Context context, String selection, String[] paramArrayOfString, String sortOrder) { + String selectionStatement = "is_music=1 AND title != ''"; if (!TextUtils.isEmpty(selection)) { selectionStatement = selectionStatement + " AND " + selection; } - Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[]{"_id", "title", "artist", "album", "duration", "track", "artist_id", "album_id"}, selectionStatement, paramArrayOfString, songSortOrder); + return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[]{"_id", "title", "artist", "album", "duration", "track", "artist_id", "album_id"}, selectionStatement, paramArrayOfString, sortOrder); + + } - return cursor; + public static Song songFromFile(String filePath) { + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + mmr.setDataSource(filePath); + return new Song( + -1, + -1, + -1, + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE), + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST), + mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM), + Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)), + 0 + ); } } diff --git a/app/src/main/java/com/naman14/timber/dialogs/AddPlaylistDialog.java b/app/src/main/java/com/naman14/timber/dialogs/AddPlaylistDialog.java index fdb6d67b4..a402c5aac 100644 --- a/app/src/main/java/com/naman14/timber/dialogs/AddPlaylistDialog.java +++ b/app/src/main/java/com/naman14/timber/dialogs/AddPlaylistDialog.java @@ -2,8 +2,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; import android.view.View; import com.afollestad.materialdialogs.MaterialDialog; diff --git a/app/src/main/java/com/naman14/timber/dialogs/CreatePlaylistDialog.java b/app/src/main/java/com/naman14/timber/dialogs/CreatePlaylistDialog.java index 0993c5ab6..ee122b93e 100644 --- a/app/src/main/java/com/naman14/timber/dialogs/CreatePlaylistDialog.java +++ b/app/src/main/java/com/naman14/timber/dialogs/CreatePlaylistDialog.java @@ -2,8 +2,8 @@ import android.app.Dialog; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; diff --git a/app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java b/app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java new file mode 100644 index 000000000..659f62226 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/dialogs/LastFmLoginDialog.java @@ -0,0 +1,60 @@ +package com.naman14.timber.dialogs; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.ProgressDialog; +import android.os.Bundle; +import androidx.annotation.NonNull; +import android.widget.EditText; +import android.widget.Toast; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.naman14.timber.R; +import com.naman14.timber.fragments.SettingsFragment; +import com.naman14.timber.lastfmapi.LastFmClient; +import com.naman14.timber.lastfmapi.callbacks.UserListener; +import com.naman14.timber.lastfmapi.models.UserLoginQuery; + +/** + * Created by christoph on 17.07.16. + */ +public class LastFmLoginDialog extends DialogFragment { + public static final String FRAGMENT_NAME = "LastFMLogin"; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new MaterialDialog.Builder(getActivity()). + positiveText("Login"). + negativeText(getString(R.string.cancel)). + title(getString(R.string.lastfm_login)). + customView(R.layout.dialog_lastfm_login, false). + onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + String username = ((EditText) dialog.findViewById(R.id.lastfm_username)).getText().toString(); + String password = ((EditText) dialog.findViewById(R.id.lastfm_password)).getText().toString(); + if (username.length() == 0 || password.length() == 0) return; + final ProgressDialog progressDialog = new ProgressDialog(getActivity()); + progressDialog.setMessage("Logging in.."); + progressDialog.show(); + LastFmClient.getInstance(getActivity()).getUserLoginInfo(new UserLoginQuery(username, password), new UserListener() { + + @Override + public void userSuccess() { + progressDialog.dismiss(); + if (getTargetFragment() instanceof SettingsFragment) { + ((SettingsFragment) getTargetFragment()).updateLastFM(); + } + } + + @Override + public void userInfoFailed() { + progressDialog.dismiss(); + Toast.makeText(getTargetFragment().getActivity(), getString(R.string.lastfm_login_failture), Toast.LENGTH_SHORT).show(); + } + }); + } + }).build(); + } +} diff --git a/app/src/main/java/com/naman14/timber/dialogs/StorageSelectDialog.java b/app/src/main/java/com/naman14/timber/dialogs/StorageSelectDialog.java new file mode 100644 index 000000000..b88cd707e --- /dev/null +++ b/app/src/main/java/com/naman14/timber/dialogs/StorageSelectDialog.java @@ -0,0 +1,72 @@ +package com.naman14.timber.dialogs; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Environment; +import androidx.appcompat.app.AlertDialog; + +import com.naman14.timber.R; + +import java.io.File; +import java.io.FileFilter; + +/** + * Created by nv95 on 06.12.16. + */ + +public class StorageSelectDialog implements DialogInterface.OnClickListener { + + private final AlertDialog mDialog; + private final File[] mStorages; + private OnDirSelectListener mDirSelectListener; + + public StorageSelectDialog(final Context context) { + mStorages = getAvailableStorages(context); + String[] names = new String[mStorages.length]; + for (int i=0;i songList = ArtistSongLoader.getSongsForArtist(getActivity(), artistID); + mAdapter = new ArtistSongAdapter(getActivity(), songList, artistID); collapsingToolbarLayout.setTitle(artist.name); @@ -188,6 +201,32 @@ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { }); } + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.artist_detail, menu); + if (getActivity() != null) + ATE.applyMenu(getActivity(), "dark_theme", menu); + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.popup_song_addto_queue: + MusicPlayer.addToQueue(getContext(), mAdapter.getSongIds(), -1, TimberUtils.IdType.NA); + break; + case R.id.popup_song_addto_playlist: + AddPlaylistDialog.newInstance(mAdapter.getSongIds()).show(getActivity().getSupportFragmentManager(), "ADD_PLAYLIST"); + break; + } + return super.onOptionsItemSelected(item); + } + @Override public void onResume() { super.onResume(); diff --git a/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java b/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java index 2acebf1a3..f2a138e87 100644 --- a/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/ArtistFragment.java @@ -17,9 +17,9 @@ import android.graphics.Rect; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -33,6 +33,7 @@ import com.naman14.timber.models.Artist; import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.SortOrder; +import com.naman14.timber.widgets.BaseRecyclerView; import com.naman14.timber.widgets.DividerItemDecoration; import com.naman14.timber.widgets.FastScroller; @@ -41,8 +42,7 @@ public class ArtistFragment extends Fragment { private ArtistAdapter mAdapter; - private RecyclerView recyclerView; - private FastScroller fastScroller; + private BaseRecyclerView recyclerView; private GridLayoutManager layoutManager; private RecyclerView.ItemDecoration itemDecoration; private PreferencesUtility mPreferences; @@ -60,8 +60,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View rootView = inflater.inflate( R.layout.fragment_recyclerview, container, false); - recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); - fastScroller = (FastScroller) rootView.findViewById(R.id.fastscroller); + recyclerView = rootView.findViewById(R.id.recyclerview); + FastScroller fastScroller = rootView.findViewById(R.id.fastscroller); + fastScroller.setRecyclerView(recyclerView); + recyclerView.setEmptyView(getActivity(), rootView.findViewById(R.id.list_empty), "No media found"); setLayoutManager(); @@ -73,11 +75,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa private void setLayoutManager() { if (isGrid) { layoutManager = new GridLayoutManager(getActivity(), 2); - fastScroller.setVisibility(View.GONE); } else { layoutManager = new GridLayoutManager(getActivity(), 1); - fastScroller.setVisibility(View.VISIBLE); - fastScroller.setRecyclerView(recyclerView); } recyclerView.setLayoutManager(layoutManager); } @@ -97,6 +96,7 @@ private void updateLayoutManager(int column) { recyclerView.setAdapter(new ArtistAdapter(getActivity(), ArtistLoader.getAllArtists(getActivity()))); layoutManager.setSpanCount(column); layoutManager.requestLayout(); + setItemDecoration(); } private void reloadAdapter() { @@ -150,10 +150,12 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; case R.id.menu_show_as_list: mPreferences.setArtistsInGrid(false); + isGrid = false; updateLayoutManager(1); return true; case R.id.menu_show_as_grid: mPreferences.setArtistsInGrid(true); + isGrid = true; updateLayoutManager(2); return true; } @@ -171,7 +173,10 @@ protected String doInBackground(String... params) { @Override protected void onPostExecute(String result) { - recyclerView.setAdapter(mAdapter); + if (mAdapter != null) { + mAdapter.setHasStableIds(true); + recyclerView.setAdapter(mAdapter); + } if (getActivity() != null) { setItemDecoration(); } diff --git a/app/src/main/java/com/naman14/timber/fragments/ArtistMusicFragment.java b/app/src/main/java/com/naman14/timber/fragments/ArtistMusicFragment.java index db6eaeb08..7a073385c 100644 --- a/app/src/main/java/com/naman14/timber/fragments/ArtistMusicFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/ArtistMusicFragment.java @@ -15,9 +15,9 @@ package com.naman14.timber.fragments; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -34,8 +34,8 @@ public class ArtistMusicFragment extends Fragment { public static RecyclerView songsRecyclerview; - long artistID = -1; - ArtistSongAdapter mSongAdapter; + private long artistID = -1; + private ArtistSongAdapter mSongAdapter; public static ArtistMusicFragment newInstance(long id) { ArtistMusicFragment fragment = new ArtistMusicFragment(); diff --git a/app/src/main/java/com/naman14/timber/fragments/FoldersFragment.java b/app/src/main/java/com/naman14/timber/fragments/FoldersFragment.java new file mode 100644 index 000000000..214a52b4f --- /dev/null +++ b/app/src/main/java/com/naman14/timber/fragments/FoldersFragment.java @@ -0,0 +1,150 @@ +package com.naman14.timber.fragments; + +import android.app.Activity; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; + +import com.afollestad.appthemeengine.ATE; +import com.naman14.timber.R; +import com.naman14.timber.adapters.FolderAdapter; +import com.naman14.timber.dialogs.StorageSelectDialog; +import com.naman14.timber.utils.PreferencesUtility; +import com.naman14.timber.widgets.DividerItemDecoration; +import com.naman14.timber.widgets.FastScroller; + +import java.io.File; + +/** + * Created by nv95 on 10.11.16. + */ + +public class FoldersFragment extends Fragment implements StorageSelectDialog.OnDirSelectListener { + + private FolderAdapter mAdapter; + private RecyclerView recyclerView; + private FastScroller fastScroller; + private ProgressBar mProgressBar; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate( + R.layout.fragment_folders, container, false); + + Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar); + ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); + + ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar(); + ab.setHomeAsUpIndicator(R.drawable.ic_menu); + ab.setDisplayHomeAsUpEnabled(true); + ab.setTitle(R.string.folders); + + recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); + fastScroller = (FastScroller) rootView.findViewById(R.id.fastscroller); + mProgressBar = (ProgressBar) rootView.findViewById(R.id.progressBar); + + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + if (getActivity() != null) + new loadFolders().execute(""); + return rootView; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + boolean dark = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("dark_theme", false); + if (dark) { + ATE.apply(this, "dark_theme"); + } else { + ATE.apply(this, "light_theme"); + } + if (mAdapter != null) { + mAdapter.applyTheme(dark); + mAdapter.notifyDataSetChanged(); + } + } + + private void setItemDecoration() { + recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); + } + + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.menu_folders, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_storages) { + new StorageSelectDialog(getActivity()) + .setDirSelectListener(this) + .show(); + } + return super.onOptionsItemSelected(item); + } + + public void updateTheme() { + Context context = getActivity(); + if (context != null) { + boolean dark = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("dark_theme", false); + mAdapter.applyTheme(dark); + } + } + + @Override + public void onDirSelected(File dir) { + mAdapter.updateDataSetAsync(dir); + } + + private class loadFolders extends AsyncTask { + + @Override + protected String doInBackground(String... params) { + Activity activity = getActivity(); + if (activity != null) { + mAdapter = new FolderAdapter(activity, new File(PreferencesUtility.getInstance(activity).getLastFolder())); + updateTheme(); + } + return "Executed"; + } + + @Override + protected void onPostExecute(String result) { + recyclerView.setAdapter(mAdapter); + //to add spacing between cards + if (getActivity() != null) { + setItemDecoration(); + } + mAdapter.notifyDataSetChanged(); + mProgressBar.setVisibility(View.GONE); + fastScroller.setVisibility(View.VISIBLE); + fastScroller.setRecyclerView(recyclerView); + } + + @Override + protected void onPreExecute() { + } + } +} diff --git a/app/src/main/java/com/naman14/timber/fragments/MainFragment.java b/app/src/main/java/com/naman14/timber/fragments/MainFragment.java index 6f9fd3373..bc3fce0f2 100644 --- a/app/src/main/java/com/naman14/timber/fragments/MainFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/MainFragment.java @@ -16,14 +16,14 @@ import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import com.google.android.material.tabs.TabLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -40,8 +40,8 @@ public class MainFragment extends Fragment { - PreferencesUtility mPreferences; - ViewPager viewPager; + private PreferencesUtility mPreferences; + private ViewPager viewPager; @Override public void onCreate(final Bundle savedInstanceState) { diff --git a/app/src/main/java/com/naman14/timber/fragments/PlaylistFragment.java b/app/src/main/java/com/naman14/timber/fragments/PlaylistFragment.java index fe24014b3..74071f83f 100644 --- a/app/src/main/java/com/naman14/timber/fragments/PlaylistFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/PlaylistFragment.java @@ -14,14 +14,20 @@ package com.naman14.timber.fragments; +import android.app.Activity; +import android.content.Intent; +import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.Toolbar; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -31,19 +37,45 @@ import com.afollestad.appthemeengine.ATE; import com.naman14.timber.R; +import com.naman14.timber.adapters.PlaylistAdapter; import com.naman14.timber.dataloaders.PlaylistLoader; import com.naman14.timber.dialogs.CreatePlaylistDialog; import com.naman14.timber.models.Playlist; import com.naman14.timber.subfragments.PlaylistPagerFragment; +import com.naman14.timber.utils.Constants; +import com.naman14.timber.utils.PreferencesUtility; +import com.naman14.timber.widgets.DividerItemDecoration; import com.naman14.timber.widgets.MultiViewPager; +import java.util.ArrayList; import java.util.List; public class PlaylistFragment extends Fragment { - int playlistcount; - FragmentStatePagerAdapter adapter; - MultiViewPager pager; + private int playlistcount; + private FragmentStatePagerAdapter adapter; + private MultiViewPager pager; + private RecyclerView recyclerView; + private GridLayoutManager layoutManager; + private RecyclerView.ItemDecoration itemDecoration; + + private PreferencesUtility mPreferences; + private boolean isGrid; + private boolean isDefault; + private boolean showAuto; + private PlaylistAdapter mAdapter; + + private List playlists = new ArrayList<>(); + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mPreferences = PreferencesUtility.getInstance(getActivity()); + isGrid = mPreferences.getPlaylistView() == Constants.PLAYLIST_VIEW_GRID; + isDefault = mPreferences.getPlaylistView() == Constants.PLAYLIST_VIEW_DEFAULT; + showAuto = mPreferences.showAutoPlaylist(); + + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -51,6 +83,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa R.layout.fragment_playlist, container, false); Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar); + pager = (MultiViewPager) rootView.findViewById(R.id.playlistpager); + recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); + + ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar(); @@ -58,11 +94,24 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa ab.setDisplayHomeAsUpEnabled(true); ab.setTitle(R.string.playlists); - final List playlists = PlaylistLoader.getPlaylists(getActivity(), true); + playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto); playlistcount = playlists.size(); - pager = (MultiViewPager) rootView.findViewById(R.id.playlistpager); + if (isDefault) { + initPager(); + } else { + initRecyclerView(); + } + + return rootView; + + } + + private void initPager() { + pager.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + recyclerView.setAdapter(null); adapter = new FragmentStatePagerAdapter(getChildFragmentManager()) { @Override @@ -78,11 +127,71 @@ public Fragment getItem(int position) { }; pager.setAdapter(adapter); pager.setOffscreenPageLimit(3); + } + + private void initRecyclerView() { + recyclerView.setVisibility(View.VISIBLE); + pager.setVisibility(View.GONE); + setLayoutManager(); + mAdapter = new PlaylistAdapter(getActivity(), playlists); + + recyclerView.setAdapter(mAdapter); + //to add spacing between cards + if (getActivity() != null) { + setItemDecoration(); + } + } + + + private void setLayoutManager() { + if (isGrid) { + layoutManager = new GridLayoutManager(getActivity(), 2); + } else { + layoutManager = new GridLayoutManager(getActivity(), 1); + } + recyclerView.setLayoutManager(layoutManager); + } + + private void setItemDecoration() { + if (isGrid) { + int spacingInPixels = getActivity().getResources().getDimensionPixelSize(R.dimen.spacing_card_album_grid); + itemDecoration = new SpacesItemDecoration(spacingInPixels); + } else { + itemDecoration = new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST); + } + recyclerView.addItemDecoration(itemDecoration); + } + + private void updateLayoutManager(int column) { + recyclerView.removeItemDecoration(itemDecoration); + recyclerView.setAdapter(new PlaylistAdapter(getActivity(), PlaylistLoader.getPlaylists(getActivity(), showAuto))); + layoutManager.setSpanCount(column); + layoutManager.requestLayout(); + setItemDecoration(); + } - return rootView; + public class SpacesItemDecoration extends RecyclerView.ItemDecoration { + private int space; + + public SpacesItemDecoration(int space) { + this.space = space; + } + + @Override + public void getItemOffsets(Rect outRect, View view, + RecyclerView parent, RecyclerView.State state) { + + + outRect.left = space; + outRect.top = space; + outRect.right = space; + outRect.bottom = space; + + } } + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -106,34 +215,103 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { } + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + if (showAuto) { + menu.findItem(R.id.action_view_auto_playlists).setTitle("Hide auto playlists"); + } else menu.findItem(R.id.action_view_auto_playlists).setTitle("Show auto playlists"); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_new_playlist: CreatePlaylistDialog.newInstance().show(getChildFragmentManager(), "CREATE_PLAYLIST"); return true; + case R.id.menu_show_as_list: + mPreferences.setPlaylistView(Constants.PLAYLIST_VIEW_LIST); + isGrid = false; + isDefault = false; + initRecyclerView(); + updateLayoutManager(1); + return true; + case R.id.menu_show_as_grid: + mPreferences.setPlaylistView(Constants.PLAYLIST_VIEW_GRID); + isGrid = true; + isDefault = false; + initRecyclerView(); + updateLayoutManager(2); + return true; + case R.id.menu_show_as_default: + mPreferences.setPlaylistView(Constants.PLAYLIST_VIEW_DEFAULT); + isDefault = true; + initPager(); + return true; + case R.id.action_view_auto_playlists: + if (showAuto) { + showAuto = false; + mPreferences.setToggleShowAutoPlaylist(false); + } else { + showAuto = true; + mPreferences.setToggleShowAutoPlaylist(true); + } + reloadPlaylists(); + getActivity().invalidateOptionsMenu(); + break; + } return super.onOptionsItemSelected(item); } public void updatePlaylists(final long id) { - final List playlists = PlaylistLoader.getPlaylists(getActivity(), true); + playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto); playlistcount = playlists.size(); - adapter.notifyDataSetChanged(); - Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - for (int i = 0; i < playlists.size(); i++) { - long playlistid = playlists.get(i).id; - if (playlistid == id) { - pager.setCurrentItem(i); - break; + + if (isDefault) { + adapter.notifyDataSetChanged(); + if (id != -1) { + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + for (int i = 0; i < playlists.size(); i++) { + long playlistid = playlists.get(i).id; + if (playlistid == id) { + pager.setCurrentItem(i); + break; + } + } } - } + }, 200); } - }, 200); + } else { + mAdapter.updateDataSet(playlists); + } + } + + public void reloadPlaylists() { + playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto); + playlistcount = playlists.size(); + + if (isDefault) { + initPager(); + } else { + initRecyclerView(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == Constants.ACTION_DELETE_PLAYLIST) { + if (resultCode == Activity.RESULT_OK) { + reloadPlaylists(); + } + + } } } diff --git a/app/src/main/java/com/naman14/timber/fragments/QueueFragment.java b/app/src/main/java/com/naman14/timber/fragments/QueueFragment.java index c0882c53f..6e5d9f33a 100644 --- a/app/src/main/java/com/naman14/timber/fragments/QueueFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/QueueFragment.java @@ -17,12 +17,11 @@ import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.v4.app.Fragment; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -36,19 +35,20 @@ import com.naman14.timber.dataloaders.QueueLoader; import com.naman14.timber.listeners.MusicStateListener; import com.naman14.timber.models.Song; +import com.naman14.timber.widgets.BaseRecyclerView; import com.naman14.timber.widgets.DragSortRecycler; public class QueueFragment extends Fragment implements MusicStateListener { private PlayingQueueAdapter mAdapter; - private RecyclerView recyclerView; + private BaseRecyclerView recyclerView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate( R.layout.fragment_queue, container, false); - Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar); + Toolbar toolbar = rootView.findViewById(R.id.toolbar); ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar(); @@ -56,9 +56,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa ab.setDisplayHomeAsUpEnabled(true); ab.setTitle(R.string.playing_queue); - recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); + recyclerView = rootView.findViewById(R.id.recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setItemAnimator(null); + recyclerView.setEmptyView(getActivity(), rootView.findViewById(R.id.list_empty), "No songs in queue"); new loadQueueSongs().execute(""); ((BaseActivity) getActivity()).setMusicStateListenerListener(this); diff --git a/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java b/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java index 323e6ac4b..49bcb4d60 100644 --- a/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/SettingsFragment.java @@ -14,6 +14,7 @@ package com.naman14.timber.fragments; +import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; import android.os.Bundle; @@ -29,7 +30,10 @@ import com.afollestad.appthemeengine.prefs.ATEColorPreference; import com.afollestad.materialdialogs.color.ColorChooserDialog; import com.naman14.timber.R; +import com.naman14.timber.activities.DonateActivity; import com.naman14.timber.activities.SettingsActivity; +import com.naman14.timber.dialogs.LastFmLoginDialog; +import com.naman14.timber.lastfmapi.LastFmClient; import com.naman14.timber.utils.Constants; import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.PreferencesUtility; @@ -37,16 +41,24 @@ public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String NOW_PLAYING_SELECTOR = "now_playing_selector"; + private static final String LASTFM_LOGIN = "lastfm_login"; + + private static final String LOCKSCREEN = "show_albumart_lockscreen"; + private static final String XPOSED = "toggle_xposed_trackselector"; + private static final String KEY_ABOUT = "preference_about"; private static final String KEY_SOURCE = "preference_source"; private static final String KEY_THEME = "theme_preference"; private static final String TOGGLE_ANIMATIONS = "toggle_animations"; private static final String TOGGLE_SYSTEM_ANIMATIONS = "toggle_system_animations"; private static final String KEY_START_PAGE = "start_page_preference"; - Preference nowPlayingSelector; - SwitchPreference toggleAnimations; - ListPreference themePreference, startPagePreference; - PreferencesUtility mPreferences; + private boolean lastFMlogedin; + + private Preference nowPlayingSelector, lastFMlogin, lockscreen, xposed; + + private SwitchPreference toggleAnimations; + private ListPreference themePreference, startPagePreference; + private PreferencesUtility mPreferences; private String mAteKey; @Override @@ -57,13 +69,18 @@ public void onCreate(Bundle savedInstanceState) { mPreferences = PreferencesUtility.getInstance(getActivity()); + lockscreen = findPreference(LOCKSCREEN); nowPlayingSelector = findPreference(NOW_PLAYING_SELECTOR); + + xposed = findPreference(XPOSED); + + lastFMlogin = findPreference(LASTFM_LOGIN); + updateLastFM(); // themePreference = (ListPreference) findPreference(KEY_THEME); startPagePreference = (ListPreference) findPreference(KEY_START_PAGE); nowPlayingSelector.setIntent(NavigationUtils.getNavigateToStyleSelectorIntent(getActivity(), Constants.SETTINGS_STYLE_SELECTOR_NOWPLAYING)); - PreferencesUtility.getInstance(getActivity()).setOnSharedPreferenceChangeListener(this); setPreferenceClickListeners(); } @@ -108,6 +125,53 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { return true; } }); + + Intent restoreIntent = new Intent(getActivity(), DonateActivity.class); + restoreIntent.putExtra("title", "Restoring purchases.."); + restoreIntent.setAction("restore"); + + findPreference("support_development").setIntent(new Intent(getActivity(), DonateActivity.class)); + findPreference("restore_purchases").setIntent(restoreIntent); + + lockscreen.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Bundle extras = new Bundle(); + extras.putBoolean("lockscreen",(boolean)newValue); + mPreferences.updateService(extras); + return true; + } + }); + + xposed.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Bundle extras = new Bundle(); + extras.putBoolean("xtrack",(boolean)newValue); + mPreferences.updateService(extras); + return true; + } + }); + + lastFMlogin.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + if (lastFMlogedin) { + LastFmClient.getInstance(getActivity()).logout(); + Bundle extras = new Bundle(); + extras.putString("lf_token","logout"); + extras.putString("lf_user",null); + mPreferences.updateService(extras); + updateLastFM(); + } else { + LastFmLoginDialog lastFmLoginDialog = new LastFmLoginDialog(); + lastFmLoginDialog.show(getChildFragmentManager(), LastFmLoginDialog.FRAGMENT_NAME); + + } + return true; + } + }); + } @Override @@ -187,4 +251,16 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } + public void updateLastFM() { + String username = LastFmClient.getInstance(getActivity()).getUsername(); + if (username != null) { + lastFMlogedin = true; + lastFMlogin.setTitle("Logout"); + lastFMlogin.setSummary(String.format(getString(R.string.lastfm_loged_in),username)); + } else { + lastFMlogedin = false; + lastFMlogin.setTitle("Login"); + lastFMlogin.setSummary(getString(R.string.lastfm_pref)); + } + } } diff --git a/app/src/main/java/com/naman14/timber/fragments/SimilarArtistFragment.java b/app/src/main/java/com/naman14/timber/fragments/SimilarArtistFragment.java index e22c32e12..836b39df8 100644 --- a/app/src/main/java/com/naman14/timber/fragments/SimilarArtistFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/SimilarArtistFragment.java @@ -15,7 +15,7 @@ package com.naman14.timber.fragments; import android.os.Bundle; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -31,7 +31,7 @@ public class SimilarArtistFragment extends Fragment { - long artistID = -1; + private long artistID = -1; public static SimilarArtistFragment newInstance(long id) { SimilarArtistFragment fragment = new SimilarArtistFragment(); diff --git a/app/src/main/java/com/naman14/timber/fragments/SongsFragment.java b/app/src/main/java/com/naman14/timber/fragments/SongsFragment.java index 61f8702b9..ecccb2040 100644 --- a/app/src/main/java/com/naman14/timber/fragments/SongsFragment.java +++ b/app/src/main/java/com/naman14/timber/fragments/SongsFragment.java @@ -16,10 +16,10 @@ import android.os.AsyncTask; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; + import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -35,6 +35,7 @@ import com.naman14.timber.models.Song; import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.SortOrder; +import com.naman14.timber.widgets.BaseRecyclerView; import com.naman14.timber.widgets.DividerItemDecoration; import com.naman14.timber.widgets.FastScroller; @@ -43,7 +44,7 @@ public class SongsFragment extends Fragment implements MusicStateListener { private SongsListAdapter mAdapter; - private RecyclerView recyclerView; + private BaseRecyclerView recyclerView; private PreferencesUtility mPreferences; @Override @@ -57,9 +58,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View rootView = inflater.inflate( R.layout.fragment_recyclerview, container, false); - recyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); + recyclerView = rootView.findViewById(R.id.recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - FastScroller fastScroller = (FastScroller) rootView.findViewById(R.id.fastscroller); + recyclerView.setEmptyView(getActivity(), rootView.findViewById(R.id.list_empty), "No media found"); + FastScroller fastScroller = rootView.findViewById(R.id.fastscroller); fastScroller.setRecyclerView(recyclerView); new loadSongs().execute(""); @@ -145,7 +147,7 @@ private class loadSongs extends AsyncTask { @Override protected String doInBackground(String... params) { if (getActivity() != null) - mAdapter = new SongsListAdapter((AppCompatActivity) getActivity(), SongLoader.getAllSongs(getActivity()), false); + mAdapter = new SongsListAdapter((AppCompatActivity) getActivity(), SongLoader.getAllSongs(getActivity()), false, false); return "Executed"; } diff --git a/app/src/main/java/com/naman14/timber/helpers/MediaButtonIntentReceiver.java b/app/src/main/java/com/naman14/timber/helpers/MediaButtonIntentReceiver.java index 63bf009c6..92d66d21a 100644 --- a/app/src/main/java/com/naman14/timber/helpers/MediaButtonIntentReceiver.java +++ b/app/src/main/java/com/naman14/timber/helpers/MediaButtonIntentReceiver.java @@ -18,7 +18,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.WakeLock; -import android.support.v4.content.WakefulBroadcastReceiver; +import androidx.legacy.content.WakefulBroadcastReceiver; import android.util.Log; import android.view.KeyEvent; diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java index 534aa94bc..045e41891 100644 --- a/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java +++ b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmClient.java @@ -1,79 +1,281 @@ -/* - * 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.lastfmapi; - -import android.content.Context; -import android.util.Log; - -import com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener; -import com.naman14.timber.lastfmapi.models.AlbumInfo; -import com.naman14.timber.lastfmapi.models.AlbumQuery; -import com.naman14.timber.lastfmapi.models.ArtistInfo; -import com.naman14.timber.lastfmapi.models.ArtistQuery; - -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; - -public class LastFmClient { - - public static final String BASE_API_URL = "http://ws.audioscrobbler.com/2.0"; - - private static LastFmClient sInstance; - private LastFmRestService mRestService; - - private static final Object sLock = new Object(); - - public static LastFmClient getInstance(Context context) { - synchronized (sLock) { - if (sInstance == null) { - sInstance = new LastFmClient(); - sInstance.mRestService = RestServiceFactory.create(context, BASE_API_URL, LastFmRestService.class); - } - return sInstance; - } - } - - public void getAlbumInfo(AlbumQuery albumQuery) { - mRestService.getAlbumInfo(albumQuery.mArtist, albumQuery.mALbum, new Callback() { - @Override - public void success(AlbumInfo albumInfo, Response response) { - - } - - @Override - public void failure(RetrofitError error) { - - error.printStackTrace(); - } - }); - } - - public void getArtistInfo(ArtistQuery artistQuery, final ArtistInfoListener listener) { - mRestService.getArtistInfo(artistQuery.mArtist, new Callback() { - @Override - public void success(ArtistInfo artistInfo, Response response) { - listener.artistInfoSucess(artistInfo.mArtist); - } - - @Override - public void failure(RetrofitError error) { - listener.artistInfoFailed(); - Log.d("lol", "failed"); - error.printStackTrace(); - } - }); - } -} +/* + * 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.lastfmapi; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; + +import com.naman14.timber.lastfmapi.callbacks.AlbumInfoListener; +import com.naman14.timber.lastfmapi.callbacks.ArtistInfoListener; +import com.naman14.timber.lastfmapi.callbacks.UserListener; +import com.naman14.timber.lastfmapi.models.AlbumInfo; +import com.naman14.timber.lastfmapi.models.AlbumQuery; +import com.naman14.timber.lastfmapi.models.ArtistInfo; +import com.naman14.timber.lastfmapi.models.ArtistQuery; +import com.naman14.timber.lastfmapi.models.LastfmUserSession; +import com.naman14.timber.lastfmapi.models.ScrobbleInfo; +import com.naman14.timber.lastfmapi.models.ScrobbleQuery; +import com.naman14.timber.lastfmapi.models.UserLoginInfo; +import com.naman14.timber.lastfmapi.models.UserLoginQuery; +import com.naman14.timber.utils.PreferencesUtility; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashSet; +import java.util.Map; +import java.util.TreeMap; + +import retrofit.Callback; +import retrofit.RetrofitError; +import retrofit.client.Response; + +public class LastFmClient { + + //TODO update the api keys + public static final String API_KEY = "62ac1851456e4558bef1c41747b1aec2"; + public static final String API_SECRET = "b4ae8965723d67fb18e35d207014d6f3"; + + public static final String JSON = "json"; + + public static final String BASE_API_URL = "http://ws.audioscrobbler.com/2.0"; + public static final String BASE_SECURE_API_URL = "https://ws.audioscrobbler.com/2.0"; + + public static final String PREFERENCES_NAME = "Lastfm"; + static final String PREFERENCE_CACHE_NAME = "Cache"; + + private static LastFmClient sInstance; + private LastFmRestService mRestService; + private LastFmUserRestService mUserRestService; + + private HashSet queries; + private boolean isUploading = false; + + private Context context; + + private LastfmUserSession mUserSession; + private static final Object sLock = new Object(); + + public static LastFmClient getInstance(Context context) { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new LastFmClient(); + sInstance.context = context; + sInstance.mRestService = RestServiceFactory.createStatic(context, BASE_API_URL, LastFmRestService.class); + sInstance.mUserRestService = RestServiceFactory.create(context, BASE_SECURE_API_URL, LastFmUserRestService.class); + sInstance.mUserSession = LastfmUserSession.getSession(context); + + } + return sInstance; + } + } + + private static String generateMD5(String in) { + try { + byte[] bytesOfMessage = in.getBytes("UTF-8"); + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(bytesOfMessage); + String out = ""; + for (byte symbol : digest) { + out += String.format("%02X", symbol); + } + return out; + } catch (UnsupportedEncodingException | NoSuchAlgorithmException ignored) { + return null; + } + + + } + + public void getAlbumInfo(AlbumQuery albumQuery, final AlbumInfoListener listener) { + mRestService.getAlbumInfo(albumQuery.mArtist, albumQuery.mALbum, new Callback() { + @Override + public void success(AlbumInfo albumInfo, Response response) { + listener.albumInfoSuccess(albumInfo.mAlbum); + } + + @Override + public void failure(RetrofitError error) { + listener.albumInfoFailed(); + error.printStackTrace(); + } + }); + } + + public void getArtistInfo(ArtistQuery artistQuery, final ArtistInfoListener listener) { + mRestService.getArtistInfo(artistQuery.mArtist, new Callback() { + @Override + public void success(ArtistInfo artistInfo, Response response) { + listener.artistInfoSucess(artistInfo.mArtist); + } + + @Override + public void failure(RetrofitError error) { + listener.artistInfoFailed(); + error.printStackTrace(); + } + }); + } + + public void getUserLoginInfo(UserLoginQuery userLoginQuery, final UserListener listener) { + mUserRestService.getUserLoginInfo(UserLoginQuery.Method, JSON, API_KEY, generateMD5(userLoginQuery.getSignature()), userLoginQuery.mUsername, userLoginQuery.mPassword, new Callback() { + @Override + public void success(UserLoginInfo userLoginInfo, Response response) { + Log.d("Logedin", userLoginInfo.mSession.mToken + " " + userLoginInfo.mSession.mUsername); + Bundle extras = new Bundle(); + extras.putString("lf_token",userLoginInfo.mSession.mToken); + extras.putString("lf_user",userLoginInfo.mSession.mUsername); + PreferencesUtility.getInstance(context).updateService(extras); + mUserSession = userLoginInfo.mSession; + mUserSession.update(context); + listener.userSuccess(); + } + + @Override + public void failure(RetrofitError error) { + listener.userInfoFailed(); + } + }); + } + + public void Scrobble(final ScrobbleQuery scrobbleQuery) { + if (mUserSession.isLogedin()) + new ScrobbleUploader(scrobbleQuery); + } + + private class ScrobbleUploader { + boolean cachedirty = false; + ScrobbleQuery newquery; + SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + + ScrobbleUploader(ScrobbleQuery query) { + if (queries == null) { + queries = new HashSet<>(); + queries.addAll(preferences.getStringSet(PREFERENCE_CACHE_NAME, new HashSet())); + } + if (query != null) { + synchronized (sLock) { + if (isUploading) { + cachedirty = true; + queries.add(query.toString()); + save(); + return; + } + } + newquery = query; + } + upload(); + } + + void upload() { + synchronized (sLock) { + isUploading = true; + } + int size = queries.size(); + if (size == 0 && newquery == null) return; + //Max 50 Scrobbles per Request (restriction by LastFM) + if (size > 50) size = 50; + if (newquery != null && size > 49) size = 49; + final String currentqueries[] = new String[size]; + int n = 0; + for (String t : queries) { + currentqueries[n++] = t; + if (n >= size) break; + } + + TreeMap fields = new TreeMap<>(); + fields.put("method", ScrobbleQuery.Method); + fields.put("api_key", API_KEY); + fields.put("sk", mUserSession.mToken); + + int i = 0; + for (String squery : currentqueries) { + ScrobbleQuery query = new ScrobbleQuery(squery); + fields.put("artist[" + i + ']', query.mArtist); + fields.put("track[" + i + ']', query.mTrack); + fields.put("timestamp[" + i + ']', Long.toString(query.mTimestamp)); + i++; + } + if (newquery != null) { + fields.put("artist[" + i + ']', newquery.mArtist); + fields.put("track[" + i + ']', newquery.mTrack); + fields.put("timestamp[" + i + ']', Long.toString(newquery.mTimestamp)); + } + String sig = ""; + for (Map.Entry ent : fields.entrySet()) { + sig += ent.getKey() + ent.getValue(); + } + sig += API_SECRET; + mUserRestService.getScrobbleInfo(generateMD5(sig), JSON, fields, new Callback() { + @Override + public void success(ScrobbleInfo scrobbleInfo, Response response) { + synchronized (sLock) { + isUploading = false; + cachedirty = true; + if (newquery != null) newquery = null; + + for (String squery : currentqueries) { + queries.remove(squery); + } + if (queries.size() > 0) + upload(); + else + save(); + + } + } + + @Override + public void failure(RetrofitError error) { + synchronized (sLock) { + isUploading = false; + //Max 500 scrobbles in Cache + if (newquery != null && queries.size() <= 500) + queries.add(newquery.toString()); + + if (cachedirty) + save(); + } + } + }); + + + } + + void save() { + if (!cachedirty) return; + SharedPreferences.Editor editor = preferences.edit(); + editor.putStringSet(PREFERENCE_CACHE_NAME, queries); + editor.apply(); + } + + } + + public void logout() { + this.mUserSession.mToken = null; + this.mUserSession.mUsername = null; + SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.clear(); + editor.apply(); + } + + public String getUsername() { + if (mUserSession != null) return mUserSession.mUsername; + return null; + } +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java new file mode 100644 index 000000000..ae067a24d --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/LastFmUserRestService.java @@ -0,0 +1,29 @@ +package com.naman14.timber.lastfmapi; + +import com.naman14.timber.lastfmapi.models.ScrobbleInfo; +import com.naman14.timber.lastfmapi.models.UserLoginInfo; + +import java.util.Map; + +import retrofit.Callback; +import retrofit.http.Field; +import retrofit.http.FieldMap; +import retrofit.http.FormUrlEncoded; +import retrofit.http.POST; + +/** + * Created by christoph on 17.07.16. + */ +public interface LastFmUserRestService { + + String BASE = "/"; + + @POST(BASE) + @FormUrlEncoded + void getUserLoginInfo(@Field("method") String method, @Field("format") String format, @Field("api_key") String apikey, @Field("api_sig") String apisig, @Field("username") String username, @Field("password") String password, Callback callback); + + @POST(BASE) + @FormUrlEncoded + void getScrobbleInfo(@Field("api_sig") String apisig, @Field("format") String format, @FieldMap Map fields, Callback callback); + +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java b/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java index 297cea1ad..63f02f452 100644 --- a/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java +++ b/app/src/main/java/com/naman14/timber/lastfmapi/RestServiceFactory.java @@ -16,6 +16,7 @@ import android.content.Context; +import com.naman14.timber.utils.PreferencesUtility; import com.squareup.okhttp.Cache; import com.squareup.okhttp.OkHttpClient; @@ -29,7 +30,7 @@ public class RestServiceFactory { private static final String TAG_OK_HTTP = "OkHttp"; private static final long CACHE_SIZE = 1024 * 1024; - public static T create(final Context context, String baseUrl, Class clazz) { + public static T createStatic(final Context context, String baseUrl, Class clazz) { final OkHttpClient okHttpClient = new OkHttpClient(); okHttpClient.setCache(new Cache(context.getApplicationContext().getCacheDir(), @@ -37,10 +38,15 @@ public static T create(final Context context, String baseUrl, Class clazz okHttpClient.setConnectTimeout(40, TimeUnit.SECONDS); RequestInterceptor interceptor = new RequestInterceptor() { + PreferencesUtility prefs = PreferencesUtility.getInstance(context); + @Override public void intercept(RequestFacade request) { //7-days cache - request.addHeader("Cache-Control", String.format("max-age=%d,max-stale=%d", Integer.valueOf(60 * 60 * 24 * 7), Integer.valueOf(31536000))); + request.addHeader("Cache-Control", + String.format("max-age=%d,%smax-stale=%d", + Integer.valueOf(60 * 60 * 24 * 7), + prefs.loadArtistAndAlbumImages() ? "" : "only-if-cached,", Integer.valueOf(31536000))); request.addHeader("Connection", "keep-alive"); } }; @@ -56,5 +62,16 @@ public void intercept(RequestFacade request) { } + public static T create(final Context context, String baseUrl, Class clazz) { + + RestAdapter.Builder builder = new RestAdapter.Builder() + .setEndpoint(baseUrl); + + return builder + .build() + .create(clazz); + + } + } diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/AlbuminfoListener.java b/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/AlbumInfoListener.java similarity index 89% rename from app/src/main/java/com/naman14/timber/lastfmapi/callbacks/AlbuminfoListener.java rename to app/src/main/java/com/naman14/timber/lastfmapi/callbacks/AlbumInfoListener.java index 1c7e7ba3c..a68ebbad2 100644 --- a/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/AlbuminfoListener.java +++ b/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/AlbumInfoListener.java @@ -16,9 +16,9 @@ import com.naman14.timber.lastfmapi.models.LastfmAlbum; -public interface AlbuminfoListener { +public interface AlbumInfoListener { - void albumInfoSucess(LastfmAlbum album); + void albumInfoSuccess(LastfmAlbum album); void albumInfoFailed(); diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java b/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java new file mode 100644 index 000000000..4c9949aa3 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/callbacks/UserListener.java @@ -0,0 +1,13 @@ +package com.naman14.timber.lastfmapi.callbacks; + + + +/** + * Created by christoph on 17.07.16. + */ +public interface UserListener { + void userSuccess(); + + void userInfoFailed(); + +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmAlbum.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmAlbum.java index 051d9bc72..402ba4818 100644 --- a/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmAlbum.java +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmAlbum.java @@ -14,5 +14,15 @@ package com.naman14.timber.lastfmapi.models; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + public class LastfmAlbum { + private static final String IMAGE = "image"; + + @SerializedName(IMAGE) + public List mArtwork; + + // Only needed fields have been defined. See https://www.last.fm/api/show/album.getInfo } diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java new file mode 100644 index 000000000..b703f8dbb --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/LastfmUserSession.java @@ -0,0 +1,48 @@ +package com.naman14.timber.lastfmapi.models; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.google.gson.annotations.SerializedName; +import com.naman14.timber.lastfmapi.LastFmClient; + +/** + * Created by christoph on 17.07.16. + */ +public class LastfmUserSession { + private static final String USERNAME = "name"; + private static final String TOKEN = "key"; + private static LastfmUserSession session; + + + public static LastfmUserSession getSession(Context context) { + if (session != null) return session; + SharedPreferences preferences = context.getSharedPreferences(LastFmClient.PREFERENCES_NAME, Context.MODE_PRIVATE); + session = new LastfmUserSession(); + session.mToken = preferences.getString(TOKEN, null); + session.mUsername = preferences.getString(USERNAME, null); + return session; + } + + public boolean isLogedin(){ + return session.mToken != null && session.mUsername != null; + } + + public void update(Context context) { + SharedPreferences preferences = context.getSharedPreferences(LastFmClient.PREFERENCES_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + if (this.mToken == null || this.mUsername == null) { + editor.clear(); + } else { + editor.putString(TOKEN, this.mToken); + editor.putString(USERNAME, this.mUsername); + } + editor.apply(); + } + + @SerializedName(USERNAME) + public String mUsername; + + @SerializedName(TOKEN) + public String mToken; +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java new file mode 100644 index 000000000..1db5a066d --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleInfo.java @@ -0,0 +1,7 @@ +package com.naman14.timber.lastfmapi.models; + +/** + * Created by christoph on 17.07.16. + */ +public class ScrobbleInfo { +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java new file mode 100644 index 000000000..a9351f5bb --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/ScrobbleQuery.java @@ -0,0 +1,52 @@ +package com.naman14.timber.lastfmapi.models; + +import com.google.gson.annotations.SerializedName; +import com.naman14.timber.lastfmapi.LastFmClient; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +/** + * Created by christoph on 17.07.16. + */ +public class ScrobbleQuery { + private static final String ARTIST_NAME = "artist"; + private static final String TRACK_NAME = "track"; + private static final String TIMESTAMP_NAME = "timestamp"; + + @SerializedName(ARTIST_NAME) + public String mArtist; + + @SerializedName(TRACK_NAME) + public String mTrack; + + @SerializedName(TIMESTAMP_NAME) + public long mTimestamp; + + public static final String Method = "track.scrobble"; + + public ScrobbleQuery(String in) { + String[] arr = in.split(","); + try { + this.mArtist = URLDecoder.decode(arr[0],"UTF-8"); + this.mTrack = URLDecoder.decode(arr[1],"UTF-8"); + this.mTimestamp = Long.parseLong(arr[2],16); + } catch (UnsupportedEncodingException ignored) { } + } + + public ScrobbleQuery(String artist, String track, long timestamp) { + this.mArtist = artist; + this.mTrack = track; + this.mTimestamp = timestamp; + } + + @Override + public String toString(){ + try { + return URLEncoder.encode(mArtist,"UTF-8")+','+URLEncoder.encode(mTrack,"UTF-8")+','+Long.toHexString(mTimestamp); + } catch (UnsupportedEncodingException ignored) { + return ""; + } + } +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java new file mode 100644 index 000000000..41a601e84 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginInfo.java @@ -0,0 +1,16 @@ +package com.naman14.timber.lastfmapi.models; + +import com.google.gson.annotations.SerializedName; +import com.naman14.timber.lastfmapi.LastFmClient; + +/** + * Created by christoph on 17.07.16. + */ +public class UserLoginInfo { + private static final String SESSION = "session"; + + @SerializedName(SESSION) + public LastfmUserSession mSession; + + +} diff --git a/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java new file mode 100644 index 000000000..8f8f9fba1 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/lastfmapi/models/UserLoginQuery.java @@ -0,0 +1,29 @@ +package com.naman14.timber.lastfmapi.models; + +import com.google.gson.annotations.SerializedName; +import com.naman14.timber.lastfmapi.LastFmClient; + +/** + * Created by christoph on 17.07.16. + */ +public class UserLoginQuery { + private static final String USERNAME_NAME = "username"; + private static final String PASSWORD_NAME = "password"; + + @SerializedName(USERNAME_NAME) + public String mUsername; + + @SerializedName(PASSWORD_NAME) + public String mPassword; + + public static final String Method = "auth.getMobileSession"; + + public UserLoginQuery(String username, String password) { + this.mUsername = username; + this.mPassword = password; + } + + public String getSignature() { + return "api_key" + LastFmClient.API_KEY + "method" + Method + "password" + mPassword + "username" + mUsername + LastFmClient.API_SECRET; + } +} diff --git a/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java b/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java index bd9f3743e..a7b496987 100644 --- a/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java +++ b/app/src/main/java/com/naman14/timber/nowplaying/BaseNowplayingFragment.java @@ -21,30 +21,40 @@ import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; +import androidx.annotation.Nullable; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import androidx.fragment.app.Fragment; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; +import android.widget.Toast; import com.afollestad.appthemeengine.ATE; import com.afollestad.appthemeengine.Config; import com.naman14.timber.MusicPlayer; +import com.naman14.timber.MusicService; import com.naman14.timber.R; import com.naman14.timber.activities.BaseActivity; import com.naman14.timber.adapters.BaseQueueAdapter; +import com.naman14.timber.adapters.SlidingQueueAdapter; import com.naman14.timber.dataloaders.QueueLoader; import com.naman14.timber.listeners.MusicStateListener; import com.naman14.timber.timely.TimelyView; import com.naman14.timber.utils.Helpers; +import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.PreferencesUtility; +import com.naman14.timber.utils.SlideTrackSwitcher; import com.naman14.timber.utils.TimberUtils; import com.naman14.timber.widgets.CircularSeekBar; import com.naman14.timber.widgets.DividerItemDecoration; @@ -62,67 +72,77 @@ public class BaseNowplayingFragment extends Fragment implements MusicStateListener { - ImageView albumart; - ImageView shuffle; - ImageView repeat; - MaterialIconView previous, next; - PlayPauseButton mPlayPause; - PlayPauseDrawable playPauseDrawable = new PlayPauseDrawable(); - FloatingActionButton playPauseFloating; - View playPauseWrapper; + private MaterialIconView previous, next; + private PlayPauseButton mPlayPause; + private PlayPauseDrawable playPauseDrawable = new PlayPauseDrawable(); + private FloatingActionButton playPauseFloating; + private View playPauseWrapper; + + private String ateKey; + private int overflowcounter = 0; + private TextView songtitle, songalbum, songartist, songduration, elapsedtime; + private SeekBar mProgress; + boolean fragmentPaused = false; + + private CircularSeekBar mCircularProgress; + private BaseQueueAdapter mAdapter; + private SlidingQueueAdapter slidingQueueAdapter; + + private TimelyView timelyView11, timelyView12, timelyView13, timelyView14, timelyView15; + private TextView hourColon; + private int[] timeArr = new int[]{0, 0, 0, 0, 0}; + private Handler mElapsedTimeHandler; + private boolean duetoplaypause = false; - String ateKey; - int accentColor; + public ImageView albumart, shuffle, repeat; + public int accentColor; + public RecyclerView recyclerView; - TextView songtitle, songalbum, songartist, songduration, elapsedtime; - SeekBar mProgress; //seekbar public Runnable mUpdateProgress = new Runnable() { @Override public void run() { + long position = MusicPlayer.position(); if (mProgress != null) { - long position = MusicPlayer.position(); mProgress.setProgress((int) position); if (elapsedtime != null && getActivity() != null) elapsedtime.setText(TimberUtils.makeShortTimeString(getActivity(), position / 1000)); } - - if (MusicPlayer.isPlaying()) { - mProgress.postDelayed(mUpdateProgress, 50); + overflowcounter--; + int delay = 250; //not sure why this delay was so high before + if (overflowcounter < 0 && !fragmentPaused) { + overflowcounter++; + mProgress.postDelayed(mUpdateProgress, delay); //delay } - } }; - CircularSeekBar mCircularProgress; + //circular seekbar public Runnable mUpdateCircularProgress = new Runnable() { @Override public void run() { - + long position = MusicPlayer.position(); if (mCircularProgress != null) { - long position = MusicPlayer.position(); mCircularProgress.setProgress((int) position); if (elapsedtime != null && getActivity() != null) elapsedtime.setText(TimberUtils.makeShortTimeString(getActivity(), position / 1000)); } - + overflowcounter--; if (MusicPlayer.isPlaying()) { - mCircularProgress.postDelayed(mUpdateCircularProgress, 50); + int delay = (int) (1500 - (position % 1000)); + if (overflowcounter < 0 && !fragmentPaused) { + overflowcounter++; + mCircularProgress.postDelayed(mUpdateCircularProgress, delay); + } } } }; - RecyclerView recyclerView; - BaseQueueAdapter mAdapter; - TimelyView timelyView11, timelyView12, timelyView13, timelyView14, timelyView15; - TextView hourColon; - int[] timeArr = new int[]{0, 0, 0, 0, 0}; - Handler mElapsedTimeHandler; public Runnable mUpdateElapsedTime = new Runnable() { @Override public void run() { @@ -155,7 +175,7 @@ public void run() { } }; - private boolean duetoplaypause = false; + private final View.OnClickListener mButtonListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -180,21 +200,27 @@ public void run() { } }; + private final View.OnClickListener mFLoatingButtonListener = new View.OnClickListener() { @Override public void onClick(View v) { duetoplaypause = true; - playPauseDrawable.transformToPlay(true); - playPauseDrawable.transformToPause(true); - Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - MusicPlayer.playOrPause(); - if (recyclerView != null && recyclerView.getAdapter() != null) - recyclerView.getAdapter().notifyDataSetChanged(); - } - }, 250); + if(MusicPlayer.getCurrentTrack() == null) { + Toast.makeText(getContext(), getString(R.string.now_playing_no_track_selected), Toast.LENGTH_SHORT).show(); + } else { + playPauseDrawable.transformToPlay(true); + playPauseDrawable.transformToPause(true); + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + MusicPlayer.playOrPause(); + if (recyclerView != null && recyclerView.getAdapter() != null) + recyclerView.getAdapter().notifyDataSetChanged(); + } + }, 250); + } + } @@ -207,6 +233,51 @@ public void onCreate(@Nullable Bundle savedInstanceState) { accentColor = Config.accentColor(getActivity(), ateKey); } + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.now_playing, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_go_to_album: + NavigationUtils.goToAlbum(getContext(), MusicPlayer.getCurrentAlbumId()); + break; + case R.id.menu_go_to_artist: + NavigationUtils.goToArtist(getContext(), MusicPlayer.getCurrentArtistId()); + break; + case R.id.action_lyrics: + NavigationUtils.goToLyrics(getContext()); + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onPause() { + super.onPause(); + fragmentPaused = true; + } + + @Override + public void onResume() { + super.onResume(); + fragmentPaused = false; + if (mProgress != null) + mProgress.postDelayed(mUpdateProgress, 10); + + if (mCircularProgress != null) + mCircularProgress.postDelayed(mUpdateCircularProgress, 10); + } + public void setSongDetails(View view) { albumart = (ImageView) view.findViewById(R.id.album_art); @@ -236,6 +307,10 @@ public void setSongDetails(View view) { recyclerView = (RecyclerView) view.findViewById(R.id.queue_recyclerview); + + songtitle.setSelected(true); + + Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar); if (toolbar != null) { ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); @@ -382,14 +457,19 @@ public void onClick(View view) { public void updateRepeatState() { if (repeat != null && getActivity() != null) { MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity()) - .setIcon(MaterialDrawableBuilder.IconValue.REPEAT) .setSizeDp(30); - if (getActivity() != null) { - if (MusicPlayer.getRepeatMode() == 0) { + if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); builder.setColor(Config.textColorPrimary(getActivity(), ateKey)); - } else builder.setColor(Config.accentColor(getActivity(), ateKey)); - } + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE); + builder.setColor(Config.accentColor(getActivity(), ateKey)); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) { + builder.setColor(Config.accentColor(getActivity(), ateKey)); + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + } + repeat.setImageDrawable(builder.build()); repeat.setOnClickListener(new View.OnClickListener() { @@ -465,6 +545,31 @@ public void onLoadingFailed(String imageUri, View view, FailReason failReason) { }); } + if (songtitle != null && MusicPlayer.getTrackName() != null) { + songtitle.setText(MusicPlayer.getTrackName()); + if(MusicPlayer.getTrackName().length() <= 23){ + songtitle.setTextSize(25); + } + else if(MusicPlayer.getTrackName().length() >= 30){ + songtitle.setTextSize(18); + } + else{ + songtitle.setTextSize(18 + (MusicPlayer.getTrackName().length() - 24)); + } + Log.v("BaseNowPlayingFrag", "Title Text Size: " + songtitle.getTextSize()); + } + if (songartist != null) { + songartist.setText(MusicPlayer.getArtistName()); + songartist.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + NavigationUtils.goToArtist(getContext(), MusicPlayer.getCurrentArtistId()); + } + }); + } + if (songalbum != null) + songalbum.setText(MusicPlayer.getAlbumName()); + } duetoplaypause = false; @@ -474,16 +579,6 @@ public void onLoadingFailed(String imageUri, View view, FailReason failReason) { if (playPauseFloating != null) updatePlayPauseFloatingButton(); - - if (songtitle != null) - songtitle.setText(MusicPlayer.getTrackName()); - - if (songalbum != null) - songalbum.setText(MusicPlayer.getAlbumName()); - - if (songartist != null) - songartist.setText(MusicPlayer.getArtistName()); - if (songduration != null && getActivity() != null) songduration.setText(TimberUtils.makeShortTimeString(getActivity(), MusicPlayer.duration() / 1000)); @@ -617,20 +712,35 @@ public void tv15(int a) { } } + protected void initGestures(View v) { + if (PreferencesUtility.getInstance(v.getContext()).isGesturesEnabled()) { + new SlideTrackSwitcher() { + @Override + public void onSwipeBottom() { + getActivity().finish(); + } + }.attach(v); + } + } + private class loadQueueSongs extends AsyncTask { @Override protected String doInBackground(String... params) { - mAdapter = new BaseQueueAdapter((AppCompatActivity) getActivity(), QueueLoader.getQueueSongs(getActivity())); - return "Executed"; + if (getActivity() != null) { + mAdapter = new BaseQueueAdapter((AppCompatActivity) getActivity(), QueueLoader.getQueueSongs(getActivity())); + return "Executed"; + } else return null; } @Override protected void onPostExecute(String result) { - recyclerView.setAdapter(mAdapter); - if (getActivity() != null) - recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); - recyclerView.scrollToPosition(MusicPlayer.getQueuePosition() - 1); + if (result != null) { + recyclerView.setAdapter(mAdapter); + if (getActivity() != null) + recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); + recyclerView.scrollToPosition(MusicPlayer.getQueuePosition() - 1); + } } diff --git a/app/src/main/java/com/naman14/timber/nowplaying/Timber1.java b/app/src/main/java/com/naman14/timber/nowplaying/Timber1.java index 60174a255..54520a406 100644 --- a/app/src/main/java/com/naman14/timber/nowplaying/Timber1.java +++ b/app/src/main/java/com/naman14/timber/nowplaying/Timber1.java @@ -36,6 +36,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa setMusicStateListener(); setSongDetails(rootView); + initGestures(rootView.findViewById(R.id.album_art)); return rootView; } diff --git a/app/src/main/java/com/naman14/timber/nowplaying/Timber2.java b/app/src/main/java/com/naman14/timber/nowplaying/Timber2.java index d4f0f5288..76c75ebcf 100644 --- a/app/src/main/java/com/naman14/timber/nowplaying/Timber2.java +++ b/app/src/main/java/com/naman14/timber/nowplaying/Timber2.java @@ -25,7 +25,9 @@ import android.view.ViewGroup; import android.widget.ImageView; +import com.afollestad.appthemeengine.Config; import com.naman14.timber.MusicPlayer; +import com.naman14.timber.MusicService; import com.naman14.timber.R; import com.naman14.timber.utils.ImageUtils; @@ -44,6 +46,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa setSongDetails(rootView); mBlurredArt = (ImageView) rootView.findViewById(R.id.album_art_blurred); + initGestures(mBlurredArt); + return rootView; } @@ -74,13 +78,23 @@ public void onClick(View view) { public void updateRepeatState() { if (repeat != null && getActivity() != null) { MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity()) - .setIcon(MaterialDrawableBuilder.IconValue.REPEAT) .setSizeDp(30); if (MusicPlayer.getRepeatMode() == 0) { builder.setColor(Color.WHITE); } else builder.setColor(accentColor); + if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + builder.setColor(Color.WHITE); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE); + builder.setColor(accentColor); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) { + builder.setColor(accentColor); + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + } + repeat.setImageDrawable(builder.build()); repeat.setOnClickListener(new View.OnClickListener() { @Override diff --git a/app/src/main/java/com/naman14/timber/nowplaying/Timber3.java b/app/src/main/java/com/naman14/timber/nowplaying/Timber3.java index 881c30fcc..a11e8df5b 100644 --- a/app/src/main/java/com/naman14/timber/nowplaying/Timber3.java +++ b/app/src/main/java/com/naman14/timber/nowplaying/Timber3.java @@ -31,6 +31,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa setMusicStateListener(); setSongDetails(rootView); + initGestures(rootView.findViewById(R.id.album_art)); + return rootView; } diff --git a/app/src/main/java/com/naman14/timber/nowplaying/Timber4.java b/app/src/main/java/com/naman14/timber/nowplaying/Timber4.java index 05d0b1bd5..bfc100fb0 100644 --- a/app/src/main/java/com/naman14/timber/nowplaying/Timber4.java +++ b/app/src/main/java/com/naman14/timber/nowplaying/Timber4.java @@ -20,16 +20,17 @@ import android.graphics.drawable.TransitionDrawable; import android.os.AsyncTask; import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import com.naman14.timber.MusicPlayer; +import com.naman14.timber.MusicService; import com.naman14.timber.R; -import com.naman14.timber.adapters.Timber4QueueAdapter; +import com.naman14.timber.adapters.SlidingQueueAdapter; import com.naman14.timber.dataloaders.QueueLoader; import com.naman14.timber.utils.ImageUtils; @@ -39,7 +40,7 @@ public class Timber4 extends BaseNowplayingFragment { ImageView mBlurredArt; RecyclerView horizontalRecyclerview; - Timber4QueueAdapter horizontalAdapter; + SlidingQueueAdapter horizontalAdapter; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -53,6 +54,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa horizontalRecyclerview = (RecyclerView) rootView.findViewById(R.id.queue_recyclerview_horizontal); setupHorizontalQueue(); + initGestures(mBlurredArt); return rootView; } @@ -84,13 +86,22 @@ public void onClick(View view) { public void updateRepeatState() { if (repeat != null && getActivity() != null) { MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity()) - .setIcon(MaterialDrawableBuilder.IconValue.REPEAT) .setSizeDp(30); if (MusicPlayer.getRepeatMode() == 0) { builder.setColor(Color.WHITE); } else builder.setColor(accentColor); + if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + builder.setColor(Color.WHITE); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE); + builder.setColor(accentColor); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) { + builder.setColor(accentColor); + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + } repeat.setImageDrawable(builder.build()); repeat.setOnClickListener(new View.OnClickListener() { @Override @@ -111,7 +122,7 @@ public void doAlbumArtStuff(Bitmap loadedImage) { private void setupHorizontalQueue() { horizontalRecyclerview.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false)); - horizontalAdapter = new Timber4QueueAdapter(getActivity(), QueueLoader.getQueueSongs(getActivity())); + horizontalAdapter = new SlidingQueueAdapter(getActivity(), QueueLoader.getQueueSongs(getActivity())); horizontalRecyclerview.setAdapter(horizontalAdapter); horizontalRecyclerview.scrollToPosition(MusicPlayer.getQueuePosition() - 3); } diff --git a/app/src/main/java/com/naman14/timber/nowplaying/Timber5.java b/app/src/main/java/com/naman14/timber/nowplaying/Timber5.java new file mode 100644 index 000000000..43d91edfa --- /dev/null +++ b/app/src/main/java/com/naman14/timber/nowplaying/Timber5.java @@ -0,0 +1,158 @@ +package com.naman14.timber.nowplaying; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.os.AsyncTask; +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.naman14.timber.MusicPlayer; +import com.naman14.timber.MusicService; +import com.naman14.timber.R; +import com.naman14.timber.adapters.SlidingQueueAdapter; +import com.naman14.timber.dataloaders.QueueLoader; +import com.naman14.timber.utils.ImageUtils; + +import net.steamcrafted.materialiconlib.MaterialDrawableBuilder; + +/** + * Created by naman on 22/02/17. + */ + +public class Timber5 extends BaseNowplayingFragment { + + ImageView mBlurredArt; + RecyclerView recyclerView; + SlidingQueueAdapter adapter; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate( + R.layout.fragment_timber5, container, false); + + setMusicStateListener(); + setSongDetails(rootView); + + mBlurredArt = (ImageView) rootView.findViewById(R.id.album_art_blurred); + recyclerView = (RecyclerView) rootView.findViewById(R.id.queue_recyclerview_horizontal) ; + initGestures(mBlurredArt); + setupSlidingQueue(); + + return rootView; + } + + @Override + public void updateShuffleState() { + if (shuffle != null && getActivity() != null) { + MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity()) + .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE) + .setSizeDp(30); + + if (MusicPlayer.getShuffleMode() == 0) { + builder.setColor(Color.WHITE); + } else builder.setColor(accentColor); + + shuffle.setImageDrawable(builder.build()); + shuffle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + MusicPlayer.cycleShuffle(); + updateShuffleState(); + updateRepeatState(); + } + }); + } + } + + @Override + public void updateRepeatState() { + if (repeat != null && getActivity() != null) { + MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity()) + .setSizeDp(30); + + if (MusicPlayer.getRepeatMode() == 0) { + builder.setColor(Color.WHITE); + } else builder.setColor(accentColor); + + if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + builder.setColor(Color.WHITE); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE); + builder.setColor(accentColor); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) { + builder.setColor(accentColor); + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + } + + repeat.setImageDrawable(builder.build()); + repeat.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + MusicPlayer.cycleRepeat(); + updateRepeatState(); + updateShuffleState(); + } + }); + } + } + + @Override + public void doAlbumArtStuff(Bitmap loadedImage) { + setBlurredAlbumArt blurredAlbumArt = new setBlurredAlbumArt(); + blurredAlbumArt.execute(loadedImage); + } + + private void setupSlidingQueue() { + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false)); + adapter = new SlidingQueueAdapter((AppCompatActivity) getActivity(), QueueLoader.getQueueSongs(getActivity())); + recyclerView.setAdapter(adapter); + recyclerView.scrollToPosition(MusicPlayer.getQueuePosition() - 3); + } + + + private class setBlurredAlbumArt extends AsyncTask { + + @Override + protected Drawable doInBackground(Bitmap... loadedImage) { + Drawable drawable = null; + try { + drawable = ImageUtils.createBlurredImageFromBitmap(loadedImage[0], getActivity(), 12); + } catch (Exception e) { + e.printStackTrace(); + } + return drawable; + } + + @Override + protected void onPostExecute(Drawable result) { + if (result != null) { + if (mBlurredArt.getDrawable() != null) { + final TransitionDrawable td = + new TransitionDrawable(new Drawable[]{ + mBlurredArt.getDrawable(), + result + }); + mBlurredArt.setImageDrawable(td); + td.startTransition(200); + + } else { + mBlurredArt.setImageDrawable(result); + } + } + } + + @Override + protected void onPreExecute() { + } + } + +} diff --git a/app/src/main/java/com/naman14/timber/nowplaying/Timber6.java b/app/src/main/java/com/naman14/timber/nowplaying/Timber6.java new file mode 100644 index 000000000..d4556b886 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/nowplaying/Timber6.java @@ -0,0 +1,124 @@ +package com.naman14.timber.nowplaying; + +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.naman14.timber.MusicPlayer; +import com.naman14.timber.MusicService; +import com.naman14.timber.R; +import com.naman14.timber.dataloaders.SongLoader; +import com.naman14.timber.models.Song; +import com.naman14.timber.utils.TimberUtils; +import com.naman14.timber.widgets.CircleImageView; + +import net.steamcrafted.materialiconlib.MaterialDrawableBuilder; + +/** + * Created by naman on 22/02/17. + */ + +public class Timber6 extends BaseNowplayingFragment { + + TextView nextSong; + CircleImageView nextArt; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate( + R.layout.fragment_timber6, container, false); + + setMusicStateListener(); + setSongDetails(rootView); + + initGestures(rootView.findViewById(R.id.album_art)); + + ((SeekBar) rootView.findViewById(R.id.song_progress)).getProgressDrawable().setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); + ((SeekBar) rootView.findViewById(R.id.song_progress)).getThumb().setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)); + + nextSong = (TextView) rootView.findViewById(R.id.title_next); + nextArt = (CircleImageView) rootView.findViewById(R.id.album_art_next); + + rootView.findViewById(R.id.nextView).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + MusicPlayer.next(); + } + }); + + return rootView; + } + + @Override + public void updateShuffleState() { + if (shuffle != null && getActivity() != null) { + MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity()) + .setIcon(MaterialDrawableBuilder.IconValue.SHUFFLE) + .setSizeDp(30); + + if (MusicPlayer.getShuffleMode() == 0) { + builder.setColor(Color.WHITE); + } else builder.setColor(accentColor); + + shuffle.setImageDrawable(builder.build()); + shuffle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + MusicPlayer.cycleShuffle(); + updateShuffleState(); + updateRepeatState(); + } + }); + } + } + + @Override + public void updateRepeatState() { + if (repeat != null && getActivity() != null) { + MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(getActivity()) + .setSizeDp(30); + + if (MusicPlayer.getRepeatMode() == 0) { + builder.setColor(Color.WHITE); + } else builder.setColor(accentColor); + + if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_NONE) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + builder.setColor(Color.WHITE); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_CURRENT) { + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT_ONCE); + builder.setColor(accentColor); + } else if (MusicPlayer.getRepeatMode() == MusicService.REPEAT_ALL) { + builder.setColor(accentColor); + builder.setIcon(MaterialDrawableBuilder.IconValue.REPEAT); + } + + repeat.setImageDrawable(builder.build()); + repeat.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + MusicPlayer.cycleRepeat(); + updateRepeatState(); + updateShuffleState(); + } + }); + } + } + + @Override + public void onMetaChanged() { + super.onMetaChanged(); + if (getActivity() != null) { + long nextId = MusicPlayer.getNextAudioId(); + Song next = SongLoader.getSongForID(getActivity(), nextId); + nextSong.setText(next.title); + nextArt.setImageURI(TimberUtils.getAlbumArtUri(next.albumId)); + } + } +} diff --git a/app/src/main/java/com/naman14/timber/slidinguppanel/SlidingUpPanelLayout.java b/app/src/main/java/com/naman14/timber/slidinguppanel/SlidingUpPanelLayout.java index e6244edaf..b26693d50 100644 --- a/app/src/main/java/com/naman14/timber/slidinguppanel/SlidingUpPanelLayout.java +++ b/app/src/main/java/com/naman14/timber/slidinguppanel/SlidingUpPanelLayout.java @@ -10,9 +10,9 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewCompat; +import androidx.core.content.ContextCompat; +import androidx.core.view.MotionEventCompat; +import androidx.core.view.ViewCompat; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; @@ -945,7 +945,7 @@ private void onPanelDragged(int newTop) { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { boolean result; - final int save = canvas.save(Canvas.CLIP_SAVE_FLAG); + final int save = canvas.save(); if (isSlidingEnabled() && mMainView == child) { // Clip against the slider; no sense drawing what will immediately be covered, diff --git a/app/src/main/java/com/naman14/timber/slidinguppanel/ViewDragHelper.java b/app/src/main/java/com/naman14/timber/slidinguppanel/ViewDragHelper.java index 518946b15..ea4d26363 100644 --- a/app/src/main/java/com/naman14/timber/slidinguppanel/ViewDragHelper.java +++ b/app/src/main/java/com/naman14/timber/slidinguppanel/ViewDragHelper.java @@ -18,10 +18,10 @@ package com.naman14.timber.slidinguppanel; import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.VelocityTrackerCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ScrollerCompat; +import androidx.core.view.MotionEventCompat; +import androidx.core.view.VelocityTrackerCompat; +import androidx.core.view.ViewCompat; +import androidx.core.widget.ScrollerCompat; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; diff --git a/app/src/main/java/com/naman14/timber/subfragments/ArtistTagFragment.java b/app/src/main/java/com/naman14/timber/subfragments/ArtistTagFragment.java index 670fd1328..990811c50 100644 --- a/app/src/main/java/com/naman14/timber/subfragments/ArtistTagFragment.java +++ b/app/src/main/java/com/naman14/timber/subfragments/ArtistTagFragment.java @@ -15,8 +15,8 @@ package com.naman14.timber.subfragments; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/com/naman14/timber/subfragments/LyricsFragment.java b/app/src/main/java/com/naman14/timber/subfragments/LyricsFragment.java new file mode 100644 index 000000000..69d1decb9 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/subfragments/LyricsFragment.java @@ -0,0 +1,126 @@ +package com.naman14.timber.subfragments; + +import android.content.CursorLoader; +import android.database.Cursor; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.naman14.timber.MusicPlayer; +import com.naman14.timber.R; +import com.naman14.timber.utils.LyricsExtractor; +import com.naman14.timber.utils.LyricsLoader; + +import java.io.File; + +import retrofit.Callback; +import retrofit.RetrofitError; +import retrofit.client.Response; + +/** + * Created by christoph on 10.12.16. + */ + +public class LyricsFragment extends Fragment { + + private String lyrics = null; + private Toolbar toolbar; + private View rootView; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + rootView = inflater.inflate(R.layout.fragment_lyrics,container,false); + + toolbar = (Toolbar) rootView.findViewById(R.id.toolbar); + setupToolbar(); + + loadLyrics(); + + return rootView; + } + + private void loadLyrics() { + + final View lyricsView = rootView.findViewById(R.id.lyrics); + final TextView poweredbyTextView = (TextView) lyricsView.findViewById(R.id.lyrics_makeitpersonal); + poweredbyTextView.setVisibility(View.GONE); + final TextView lyricsTextView = (TextView) lyricsView.findViewById(R.id.lyrics_text); + lyricsTextView.setText(getString(R.string.lyrics_loading)); + String filename = getRealPathFromURI(Uri.parse(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + MusicPlayer.getCurrentAudioId())); + if (filename != null && lyrics == null) { + lyrics = LyricsExtractor.getLyrics(new File(filename)); + } + + if (lyrics != null) { + lyricsTextView.setText(lyrics); + } else { + String artist = MusicPlayer.getArtistName(); + if (artist != null) { + int i = artist.lastIndexOf(" feat"); + if (i != -1) { + artist = artist.substring(0, i); + } + + LyricsLoader.getInstance(this.getContext()).getLyrics(artist, MusicPlayer.getTrackName(), new Callback() { + @Override + public void success(String s, Response response) { + lyrics = s; + if (s.equals("Sorry, We don't have lyrics for this song yet.\n")) { + lyricsTextView.setText(R.string.no_lyrics); + } else { + lyricsTextView.setText(s); + poweredbyTextView.setVisibility(View.VISIBLE); + } + } + + @Override + public void failure(RetrofitError error) { + lyricsTextView.setText(R.string.no_lyrics); + } + }); + + } else { + lyricsTextView.setText(R.string.no_lyrics); + } + } + } + + private void setupToolbar() { + + ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); + + final ActionBar ab = ((AppCompatActivity) getActivity()).getSupportActionBar(); + ab.setDisplayHomeAsUpEnabled(true); + if (MusicPlayer.getTrackName() != null) { + ab.setTitle(MusicPlayer.getTrackName()); + } + } + + @Override + public void onResume() { + super.onResume(); + toolbar.setBackgroundColor(Color.TRANSPARENT); + } + + private String getRealPathFromURI(Uri contentUri) { + String[] proj = {MediaStore.Audio.Media.DATA}; + CursorLoader loader = new CursorLoader(this.getContext(), contentUri, proj, null, null, null); + Cursor cursor = loader.loadInBackground(); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA); + cursor.moveToFirst(); + String result = cursor.getString(column_index); + cursor.close(); + return result; + } +} diff --git a/app/src/main/java/com/naman14/timber/subfragments/PlaylistPagerFragment.java b/app/src/main/java/com/naman14/timber/subfragments/PlaylistPagerFragment.java index e45743de8..73401e04c 100644 --- a/app/src/main/java/com/naman14/timber/subfragments/PlaylistPagerFragment.java +++ b/app/src/main/java/com/naman14/timber/subfragments/PlaylistPagerFragment.java @@ -18,8 +18,8 @@ import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; @@ -37,6 +37,7 @@ import com.naman14.timber.models.Song; import com.naman14.timber.utils.Constants; import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.PreferencesUtility; import com.naman14.timber.utils.TimberUtils; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; @@ -49,15 +50,16 @@ public class PlaylistPagerFragment extends Fragment { private static final String ARG_PAGE_NUMBER = "pageNumber"; - int[] foregroundColors = {R.color.pink_transparent, R.color.green_transparent, R.color.blue_transparent, R.color.red_transparent, R.color.purple_transparent}; - private int pageNumber, songCountInt; + private int[] foregroundColors = {R.color.pink_transparent, R.color.green_transparent, R.color.blue_transparent, R.color.red_transparent, R.color.purple_transparent}; + private int pageNumber, songCountInt, totalRuntime; private int foregroundColor; private long firstAlbumID = -1; private Playlist playlist; - private TextView playlistame, songcount, playlistnumber, playlisttype; + private TextView playlistame, songcount, playlistnumber, playlisttype, runtime; private ImageView playlistImage; private View foreground; private Context mContext; + private boolean showAuto; public static PlaylistPagerFragment newInstance(int pageNumber) { PlaylistPagerFragment fragment = new PlaylistPagerFragment(); @@ -70,9 +72,10 @@ public static PlaylistPagerFragment newInstance(int pageNumber) { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + showAuto = PreferencesUtility.getInstance(getActivity()).showAutoPlaylist(); View rootView = inflater.inflate(R.layout.fragment_playlist_pager, container, false); - final List playlists = PlaylistLoader.getPlaylists(getActivity(), true); + final List playlists = PlaylistLoader.getPlaylists(getActivity(), showAuto); pageNumber = getArguments().getInt(ARG_PAGE_NUMBER); playlist = playlists.get(pageNumber); @@ -80,6 +83,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, playlistame = (TextView) rootView.findViewById(R.id.name); playlistnumber = (TextView) rootView.findViewById(R.id.number); songcount = (TextView) rootView.findViewById(R.id.songcount); + runtime = (TextView) rootView.findViewById(R.id.runtime); playlisttype = (TextView) rootView.findViewById(R.id.playlisttype); playlistImage = (ImageView) rootView.findViewById(R.id.playlist_image); foreground = rootView.findViewById(R.id.foreground); @@ -125,23 +129,26 @@ private void setUpPlaylistDetails() { foregroundColor = foregroundColors[rndInt]; foreground.setBackgroundColor(foregroundColor); - if (pageNumber > 2) { - playlisttype.setVisibility(View.GONE); + if (showAuto) { + if (pageNumber <= 2) + playlisttype.setVisibility(View.VISIBLE); } } private String getPlaylistType() { - switch (pageNumber) { - case 0: - return Constants.NAVIGATE_PLAYLIST_LASTADDED; - case 1: - return Constants.NAVIGATE_PLAYLIST_RECENT; - case 2: - return Constants.NAVIGATE_PLAYLIST_TOPTRACKS; - default: - return Constants.NAVIGATE_PLAYLIST_USERCREATED; - } + if (showAuto) { + switch (pageNumber) { + case 0: + return Constants.NAVIGATE_PLAYLIST_LASTADDED; + case 1: + return Constants.NAVIGATE_PLAYLIST_RECENT; + case 2: + return Constants.NAVIGATE_PLAYLIST_TOPTRACKS; + default: + return Constants.NAVIGATE_PLAYLIST_USERCREATED; + } + } else return Constants.NAVIGATE_PLAYLIST_USERCREATED; } @@ -150,42 +157,63 @@ private class loadPlaylistImage extends AsyncTask { @Override protected String doInBackground(String... params) { if (getActivity() != null) { - switch (pageNumber) { - case 0: - List lastAddedSongs = LastAddedLoader.getLastAddedSongs(getActivity()); - songCountInt = lastAddedSongs.size(); - - if (songCountInt != 0) { - firstAlbumID = lastAddedSongs.get(0).albumId; - return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); - } else return "nosongs"; - case 1: - TopTracksLoader recentloader = new TopTracksLoader(getActivity(), TopTracksLoader.QueryType.RecentSongs); - List recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor()); - songCountInt = recentsongs.size(); - - if (songCountInt != 0) { - firstAlbumID = recentsongs.get(0).albumId; - return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); - } else return "nosongs"; - case 2: - TopTracksLoader topTracksLoader = new TopTracksLoader(getActivity(), TopTracksLoader.QueryType.TopTracks); - List topsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor()); - songCountInt = topsongs.size(); - - if (songCountInt != 0) { - firstAlbumID = topsongs.get(0).albumId; - return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); - } else return "nosongs"; - default: - List playlistsongs = PlaylistSongLoader.getSongsInPlaylist(getActivity(), playlist.id); - songCountInt = playlistsongs.size(); - - if (songCountInt != 0) { - firstAlbumID = playlistsongs.get(0).albumId; - return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); - } else return "nosongs"; - + if (showAuto) { + switch (pageNumber) { + case 0: + List lastAddedSongs = LastAddedLoader.getLastAddedSongs(getActivity()); + songCountInt = lastAddedSongs.size(); + for(Song song : lastAddedSongs) { + totalRuntime += song.duration / 1000; //for some reason default playlists have songs with durations 1000x larger than they should be + } + if (songCountInt != 0) { + firstAlbumID = lastAddedSongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + case 1: + TopTracksLoader recentloader = new TopTracksLoader(getActivity(), TopTracksLoader.QueryType.RecentSongs); + List recentsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor()); + songCountInt = recentsongs.size(); + for(Song song : recentsongs){ + totalRuntime += song.duration / 1000; + } + + if (songCountInt != 0) { + firstAlbumID = recentsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + case 2: + TopTracksLoader topTracksLoader = new TopTracksLoader(getActivity(), TopTracksLoader.QueryType.TopTracks); + List topsongs = SongLoader.getSongsForCursor(TopTracksLoader.getCursor()); + songCountInt = topsongs.size(); + for(Song song : topsongs){ + totalRuntime += song.duration / 1000; + } + if (songCountInt != 0) { + firstAlbumID = topsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + default: + List playlistsongs = PlaylistSongLoader.getSongsInPlaylist(getActivity(), playlist.id); + songCountInt = playlistsongs.size(); + for(Song song : playlistsongs){ + totalRuntime += song.duration; + } + if (songCountInt != 0) { + firstAlbumID = playlistsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; + + } + } else { + List playlistsongs = PlaylistSongLoader.getSongsInPlaylist(getActivity(), playlist.id); + songCountInt = playlistsongs.size(); + for(Song song : playlistsongs){ + totalRuntime += song.duration; + } + if (songCountInt != 0) { + firstAlbumID = playlistsongs.get(0).albumId; + return TimberUtils.getAlbumArtUri(firstAlbumID).toString(); + } else return "nosongs"; } } else return "context is null"; @@ -203,6 +231,7 @@ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { } }); songcount.setText(" " + String.valueOf(songCountInt) + " " + mContext.getString(R.string.songs)); + runtime.setText(" " + TimberUtils.makeShortTimeString(mContext, totalRuntime)); } @Override diff --git a/app/src/main/java/com/naman14/timber/subfragments/QuickControlsFragment.java b/app/src/main/java/com/naman14/timber/subfragments/QuickControlsFragment.java index e8764fba9..9f7512335 100644 --- a/app/src/main/java/com/naman14/timber/subfragments/QuickControlsFragment.java +++ b/app/src/main/java/com/naman14/timber/subfragments/QuickControlsFragment.java @@ -21,7 +21,8 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -38,6 +39,9 @@ import com.naman14.timber.listeners.MusicStateListener; import com.naman14.timber.utils.Helpers; import com.naman14.timber.utils.ImageUtils; +import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.PreferencesUtility; +import com.naman14.timber.utils.SlideTrackSwitcher; import com.naman14.timber.utils.TimberUtils; import com.naman14.timber.widgets.PlayPauseButton; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -53,6 +57,17 @@ public class QuickControlsFragment extends Fragment implements MusicStateListene public static View topContainer; private ProgressBar mProgress; private SeekBar mSeekBar; + private int overflowcounter = 0; + private PlayPauseButton mPlayPause, mPlayPauseExpanded; + private TextView mTitle, mTitleExpanded; + private TextView mArtist, mArtistExpanded; + private ImageView mAlbumArt, mBlurredArt; + private View rootView; + private View playPauseWrapper, playPauseWrapperExpanded; + private MaterialIconView previous, next; + private boolean duetoplaypause = false; + private boolean fragmentPaused = false; + public Runnable mUpdateProgress = new Runnable() { @Override @@ -62,20 +77,18 @@ public void run() { mProgress.setProgress((int) position); mSeekBar.setProgress((int) position); + overflowcounter--; if (MusicPlayer.isPlaying()) { - mProgress.postDelayed(mUpdateProgress, 50); + int delay = (int) (1500 - (position % 1000)); + if (overflowcounter < 0 && !fragmentPaused) { + overflowcounter++; + mProgress.postDelayed(mUpdateProgress, delay); + } } else mProgress.removeCallbacks(this); } }; - private PlayPauseButton mPlayPause, mPlayPauseExpanded; - private TextView mTitle, mTitleExpanded; - private TextView mArtist, mArtistExpanded; - private ImageView mAlbumArt, mBlurredArt; - private View rootView; - private View playPauseWrapper, playPauseWrapperExpanded; - private MaterialIconView previous, next; - private boolean duetoplaypause = false; + private final View.OnClickListener mPlayPauseListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -97,6 +110,7 @@ public void run() { } }; + private final View.OnClickListener mPlayPauseExpandedListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -199,9 +213,25 @@ public void run() { ((BaseActivity) getActivity()).setMusicStateListenerListener(this); + if (PreferencesUtility.getInstance(getActivity()).isGesturesEnabled()) { + new SlideTrackSwitcher() { + @Override + public void onClick() { + NavigationUtils.navigateToNowplaying(getActivity(), false); + } + }.attach(rootView.findViewById(R.id.root_view)); + } + + return rootView; } + @Override + public void onPause() { + super.onPause(); + fragmentPaused = true; + } + public void updateNowplayingCard() { mTitle.setText(MusicPlayer.getTrackName()); mArtist.setText(MusicPlayer.getArtistName()); @@ -260,6 +290,9 @@ public void onStop() { public void onResume() { super.onResume(); topContainer = rootView.findViewById(R.id.topContainer); + fragmentPaused = false; + if (mProgress != null) + mProgress.postDelayed(mUpdateProgress, 10); } diff --git a/app/src/main/java/com/naman14/timber/subfragments/StyleSelectorFragment.java b/app/src/main/java/com/naman14/timber/subfragments/StyleSelectorFragment.java index cdd0fa271..cb04593be 100644 --- a/app/src/main/java/com/naman14/timber/subfragments/StyleSelectorFragment.java +++ b/app/src/main/java/com/naman14/timber/subfragments/StyleSelectorFragment.java @@ -14,24 +14,28 @@ package com.naman14.timber.subfragments; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentStatePagerAdapter; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentStatePagerAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.naman14.timber.R; import com.naman14.timber.utils.Constants; +import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.widgets.MultiViewPager; public class StyleSelectorFragment extends Fragment { public String ACTION = "action"; - FragmentStatePagerAdapter adapter; - MultiViewPager pager; + private FragmentStatePagerAdapter adapter; + private MultiViewPager pager; private SubStyleSelectorFragment selectorFragment; + private SharedPreferences preferences; public static StyleSelectorFragment newInstance(String what) { StyleSelectorFragment fragment = new StyleSelectorFragment(); @@ -47,6 +51,7 @@ public void onCreate(Bundle savedInstanceState) { if (getArguments() != null) { ACTION = getArguments().getString(Constants.SETTINGS_STYLE_SELECTOR_WHAT); } + preferences = getActivity().getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE); } @@ -64,7 +69,7 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Override public int getCount() { - return 4; + return 6; } @Override @@ -79,18 +84,22 @@ public int getItemPosition(Object object) { } }; pager.setAdapter(adapter); + scrollToCurrentStyle(); return rootView; } public void updateCurrentStyle() { - if (selectorFragment != null) + if (selectorFragment != null) { adapter.notifyDataSetChanged(); + scrollToCurrentStyle(); + } } - public void scrollToCurrentStyle(int page) { - pager.setCurrentItem(page); + public void scrollToCurrentStyle() { + String fragmentID = preferences.getString(Constants.NOWPLAYING_FRAGMENT_ID, Constants.TIMBER3); + pager.setCurrentItem(NavigationUtils.getIntForCurrentNowplaying(fragmentID)); } } diff --git a/app/src/main/java/com/naman14/timber/subfragments/SubStyleSelectorFragment.java b/app/src/main/java/com/naman14/timber/subfragments/SubStyleSelectorFragment.java index 96472babf..13a7159aa 100644 --- a/app/src/main/java/com/naman14/timber/subfragments/SubStyleSelectorFragment.java +++ b/app/src/main/java/com/naman14/timber/subfragments/SubStyleSelectorFragment.java @@ -15,10 +15,12 @@ package com.naman14.timber.subfragments; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -26,19 +28,23 @@ import android.widget.LinearLayout; import android.widget.TextView; +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; import com.naman14.timber.R; +import com.naman14.timber.activities.DonateActivity; import com.naman14.timber.utils.Constants; +import com.naman14.timber.utils.NavigationUtils; import com.naman14.timber.utils.PreferencesUtility; public class SubStyleSelectorFragment extends Fragment { private static final String ARG_PAGE_NUMBER = "pageNumber"; private static final String WHAT = "what"; - SharedPreferences.Editor editor; - SharedPreferences preferences; - LinearLayout currentStyle; - View foreground; - ImageView styleImage; + private SharedPreferences.Editor editor; + private SharedPreferences preferences; + private LinearLayout currentStyle; + private View foreground; + private ImageView styleImage, imgLock; public static SubStyleSelectorFragment newInstance(int pageNumber, String what) { SubStyleSelectorFragment fragment = new SubStyleSelectorFragment(); @@ -56,12 +62,22 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, TextView styleName = (TextView) rootView.findViewById(R.id.style_name); styleName.setText(String.valueOf(getArguments().getInt(ARG_PAGE_NUMBER) + 1)); + preferences = getActivity().getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE); styleImage = (ImageView) rootView.findViewById(R.id.style_image); + imgLock = (ImageView) rootView.findViewById(R.id.img_lock); + styleImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - setPreferences(); + if (getArguments().getInt(ARG_PAGE_NUMBER) >= 4) { + if (isUnlocked()) { + setPreferences(); + } else { + showPurchaseDialog(); + } + } else + setPreferences(); } }); @@ -78,6 +94,12 @@ public void onClick(View view) { case 3: styleImage.setImageResource(R.drawable.timber_4_nowplaying_x); break; + case 4: + styleImage.setImageResource(R.drawable.timber_5_nowplaying_x); + break; + case 5: + styleImage.setImageResource(R.drawable.timber_6_nowplaying_x); + break; } currentStyle = (LinearLayout) rootView.findViewById(R.id.currentStyle); @@ -88,13 +110,55 @@ public void onClick(View view) { return rootView; } + private boolean isUnlocked() { + return getActivity() != null && PreferencesUtility.getInstance(getActivity()).fullUnlocked(); + } + + @Override + public void onResume() { + super.onResume(); + updateLockedStatus(); + } + + private void updateLockedStatus() { + if (getArguments().getInt(ARG_PAGE_NUMBER) >= 4 && !isUnlocked()) { + imgLock.setVisibility(View.VISIBLE); + foreground.setVisibility(View.VISIBLE); + } + else { + imgLock.setVisibility(View.GONE); + foreground.setVisibility(View.GONE); + } + } + private void showPurchaseDialog() { + MaterialDialog dialog = new MaterialDialog.Builder(getActivity()) + .title("Purchase") + .content("This now playing style is available after a one time purchase of any amount. Support development and unlock this style?") + .positiveText("Support development") + .neutralText("Restore purchases") + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + startActivity(new Intent(getActivity(), DonateActivity.class)); + dialog.dismiss(); + } + }).onNeutral(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + Intent intent = new Intent(getActivity(), DonateActivity.class); + intent.putExtra("title", "Restoring purchases.."); + intent.setAction("restore"); + startActivity(intent); + dialog.dismiss(); + } + }) + .show(); + } + public void setCurrentStyle() { - preferences = getActivity().getSharedPreferences(Constants.FRAGMENT_ID, Context.MODE_PRIVATE); String fragmentID = preferences.getString(Constants.NOWPLAYING_FRAGMENT_ID, Constants.TIMBER3); - ((StyleSelectorFragment) getParentFragment()).scrollToCurrentStyle(getIntForCurrentNowplaying(fragmentID)); - - if (getArguments().getInt(ARG_PAGE_NUMBER) == getIntForCurrentNowplaying(fragmentID)) { + if (getArguments().getInt(ARG_PAGE_NUMBER) == NavigationUtils.getIntForCurrentNowplaying(fragmentID)) { currentStyle.setVisibility(View.VISIBLE); foreground.setVisibility(View.VISIBLE); } else { @@ -127,25 +191,14 @@ private String getStyleForPageNumber() { return Constants.TIMBER3; case 3: return Constants.TIMBER4; + case 4: + return Constants.TIMBER5; + case 5: + return Constants.TIMBER6; default: return Constants.TIMBER3; } } - private int getIntForCurrentNowplaying(String nowPlaying) { - switch (nowPlaying) { - case Constants.TIMBER1: - return 0; - case Constants.TIMBER2: - return 1; - case Constants.TIMBER3: - return 2; - case Constants.TIMBER4: - return 3; - default: - return 2; - } - - } } diff --git a/app/src/main/java/com/naman14/timber/utils/ATEUtils.java b/app/src/main/java/com/naman14/timber/utils/ATEUtils.java index 239ea7c4c..4663c6723 100644 --- a/app/src/main/java/com/naman14/timber/utils/ATEUtils.java +++ b/app/src/main/java/com/naman14/timber/utils/ATEUtils.java @@ -7,10 +7,10 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.widget.DrawerLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import androidx.drawerlayout.widget.DrawerLayout; import android.view.View; import android.view.ViewGroup; import android.view.Window; diff --git a/app/src/main/java/com/naman14/timber/utils/Constants.java b/app/src/main/java/com/naman14/timber/utils/Constants.java index 3c984d29b..4856ceb88 100644 --- a/app/src/main/java/com/naman14/timber/utils/Constants.java +++ b/app/src/main/java/com/naman14/timber/utils/Constants.java @@ -22,6 +22,7 @@ public class Constants { public static final String NAVIGATE_ALBUM = "navigate_album"; public static final String NAVIGATE_ARTIST = "navigate_artist"; public static final String NAVIGATE_NOWPLAYING = "navigate_nowplaying"; + public static final String NAVIGATE_LYRICS = "navigate_lyrics"; public static final String NAVIGATE_PLAYLIST_RECENT = "navigate_playlist_recent"; public static final String NAVIGATE_PLAYLIST_LASTADDED = "navigate_playlist_lastadded"; @@ -43,6 +44,8 @@ public class Constants { public static final String TIMBER2 = "timber2"; public static final String TIMBER3 = "timber3"; public static final String TIMBER4 = "timber4"; + public static final String TIMBER5 = "timber5"; + public static final String TIMBER6 = "timber6"; public static final String NAVIGATE_SETTINGS = "navigate_settings"; public static final String NAVIGATE_SEARCH = "navigate_search"; @@ -53,4 +56,19 @@ public class Constants { public static final String SETTINGS_STYLE_SELECTOR_WHAT = "style_selector_what"; public static final String SETTINGS_STYLE_SELECTOR = "settings_style_selector"; + + public static final int PLAYLIST_VIEW_DEFAULT = 0; + public static final int PLAYLIST_VIEW_LIST = 1; + public static final int PLAYLIST_VIEW_GRID = 2; + + public static final int PLAYLIST_ALBUM_ART_TAG = 888; + public static final int ACTION_DELETE_PLAYLIST = 111; + + + public static final String ACTIVITY_TRANSITION = "activity_transition"; + + public static final int CAST_SERVER_PORT = 8080; + + + } diff --git a/app/src/main/java/com/naman14/timber/utils/FabAnimationUtils.java b/app/src/main/java/com/naman14/timber/utils/FabAnimationUtils.java index 635ac0135..991b0b7ca 100644 --- a/app/src/main/java/com/naman14/timber/utils/FabAnimationUtils.java +++ b/app/src/main/java/com/naman14/timber/utils/FabAnimationUtils.java @@ -15,9 +15,9 @@ package com.naman14.timber.utils; import android.os.Build; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorListener; -import android.support.v4.view.animation.FastOutSlowInInterpolator; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewPropertyAnimatorListener; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; diff --git a/app/src/main/java/com/naman14/timber/utils/Helpers.java b/app/src/main/java/com/naman14/timber/utils/Helpers.java index 47d726214..260b7689c 100644 --- a/app/src/main/java/com/naman14/timber/utils/Helpers.java +++ b/app/src/main/java/com/naman14/timber/utils/Helpers.java @@ -23,17 +23,20 @@ import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import com.afollestad.materialdialogs.MaterialDialog; import com.naman14.timber.R; public class Helpers { @@ -57,96 +60,27 @@ public static String getATEKey(Context context) { public static class AboutDialog extends DialogFragment { - String urlgooglelus = "https://plus.google.com/u/0/+NamanDwivedi14"; - String urlcommunity = "https://plus.google.com/communities/111029425713454201429"; - String urltwitter = "https://twitter.com/naman1405"; - String urlgithub = "https://github.com/naman14"; - String urlsource = "https://github.com/naman14/Timber/issues"; - public AboutDialog() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - LayoutInflater layoutInflater = (LayoutInflater) getActivity().getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - LinearLayout aboutBodyView = (LinearLayout) layoutInflater.inflate(R.layout.layout_about_dialog, null); - - TextView appversion = (TextView) aboutBodyView.findViewById(R.id.app_version_name); - - TextView googleplus = (TextView) aboutBodyView.findViewById(R.id.googleplus); - TextView twitter = (TextView) aboutBodyView.findViewById(R.id.twitter); - TextView github = (TextView) aboutBodyView.findViewById(R.id.github); - TextView source = (TextView) aboutBodyView.findViewById(R.id.source); - TextView community = (TextView) aboutBodyView.findViewById(R.id.feature_request); - - TextView dismiss = (TextView) aboutBodyView.findViewById(R.id.dismiss_dialog); - dismiss.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); - googleplus.setPaintFlags(googleplus.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); - twitter.setPaintFlags(twitter.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); - github.setPaintFlags(github.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); - - googleplus.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(urlgooglelus)); - startActivity(i); - } - - }); - twitter.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(urltwitter)); - startActivity(i); - } - - }); - github.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(urlgithub)); - startActivity(i); - } - - }); - source.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(urlsource)); - startActivity(i); - } - }); - community.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(urlcommunity)); - startActivity(i); - } - }); + String appName = "Timber"; try { PackageInfo pInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); String version = pInfo.versionName; int versionCode = pInfo.versionCode; - appversion.setText("Timber " + version); + appName = "Timber " + version; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } - return new AlertDialog.Builder(getActivity()) - .setView(aboutBodyView) - .create(); + return new MaterialDialog.Builder(getActivity()) + .title(appName) + .content(Html.fromHtml(getString(R.string.about_dialog_body))) + .positiveText("Dismiss") + .build(); } } diff --git a/app/src/main/java/com/naman14/timber/utils/ImageUtils.java b/app/src/main/java/com/naman14/timber/utils/ImageUtils.java index 73b940821..4bf5605fe 100644 --- a/app/src/main/java/com/naman14/timber/utils/ImageUtils.java +++ b/app/src/main/java/com/naman14/timber/utils/ImageUtils.java @@ -19,12 +19,105 @@ import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.support.v8.renderscript.RenderScript; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import android.view.View; +import android.widget.ImageView; + +import com.naman14.timber.R; +import com.naman14.timber.dataloaders.AlbumLoader; +import com.naman14.timber.lastfmapi.LastFmClient; +import com.naman14.timber.lastfmapi.callbacks.AlbumInfoListener; +import com.naman14.timber.lastfmapi.models.AlbumQuery; +import com.naman14.timber.lastfmapi.models.LastfmAlbum; +import com.naman14.timber.models.Album; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; public class ImageUtils { + private static final DisplayImageOptions lastfmDisplayImageOptions = + new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(true) + .showImageOnFail(R.drawable.ic_empty_music2) + .build(); + + private static final DisplayImageOptions diskDisplayImageOptions = + new DisplayImageOptions.Builder() + .cacheInMemory(true) + .build(); + + public static void loadAlbumArtIntoView(final long albumId, final ImageView view) { + loadAlbumArtIntoView(albumId, view, new SimpleImageLoadingListener()); + } + + public static void loadAlbumArtIntoView(final long albumId, final ImageView view, + final ImageLoadingListener listener) { + if (PreferencesUtility.getInstance(view.getContext()).alwaysLoadAlbumImagesFromLastfm()) { + loadAlbumArtFromLastfm(albumId, view, listener); + } else { + loadAlbumArtFromDiskWithLastfmFallback(albumId, view, listener); + } + } + + private static void loadAlbumArtFromDiskWithLastfmFallback(final long albumId, ImageView view, + final ImageLoadingListener listener) { + ImageLoader.getInstance() + .displayImage(TimberUtils.getAlbumArtUri(albumId).toString(), + view, + diskDisplayImageOptions, + new SimpleImageLoadingListener() { + @Override + public void onLoadingFailed(String imageUri, View view, + FailReason failReason) { + loadAlbumArtFromLastfm(albumId, (ImageView) view, listener); + listener.onLoadingFailed(imageUri, view, failReason); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + listener.onLoadingComplete(imageUri, view, loadedImage); + } + }); + } + + private static void loadAlbumArtFromLastfm(long albumId, final ImageView albumArt, final ImageLoadingListener listener) { + Album album = AlbumLoader.getAlbum(albumArt.getContext(), albumId); + LastFmClient.getInstance(albumArt.getContext()) + .getAlbumInfo(new AlbumQuery(album.title, album.artistName), + new AlbumInfoListener() { + @Override + public void albumInfoSuccess(final LastfmAlbum album) { + if (album != null) { + ImageLoader.getInstance() + .displayImage(album.mArtwork.get(4).mUrl, + albumArt, + lastfmDisplayImageOptions, new SimpleImageLoadingListener(){ + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + listener.onLoadingComplete(imageUri, view, loadedImage); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + listener.onLoadingFailed(imageUri, view, failReason); + } + }); + } + } + + @Override + public void albumInfoFailed() { } + }); + } public static Drawable createBlurredImageFromBitmap(Bitmap bitmap, Context context, int inSampleSize) { @@ -38,9 +131,9 @@ public static Drawable createBlurredImageFromBitmap(Bitmap bitmap, Context conte ByteArrayInputStream bis = new ByteArrayInputStream(imageInByte); Bitmap blurTemplate = BitmapFactory.decodeStream(bis, null, options); - final android.support.v8.renderscript.Allocation input = android.support.v8.renderscript.Allocation.createFromBitmap(rs, blurTemplate); - final android.support.v8.renderscript.Allocation output = android.support.v8.renderscript.Allocation.createTyped(rs, input.getType()); - final android.support.v8.renderscript.ScriptIntrinsicBlur script = android.support.v8.renderscript.ScriptIntrinsicBlur.create(rs, android.support.v8.renderscript.Element.U8_4(rs)); + final Allocation input = Allocation.createFromBitmap(rs, blurTemplate); + final Allocation output = Allocation.createTyped(rs, input.getType()); + final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); script.setRadius(8f); script.setInput(input); script.forEach(output); @@ -48,5 +141,4 @@ public static Drawable createBlurredImageFromBitmap(Bitmap bitmap, Context conte return new BitmapDrawable(context.getResources(), blurTemplate); } - } diff --git a/app/src/main/java/com/naman14/timber/utils/LyricsExtractor.java b/app/src/main/java/com/naman14/timber/utils/LyricsExtractor.java new file mode 100644 index 000000000..151bac513 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/utils/LyricsExtractor.java @@ -0,0 +1,227 @@ +package com.naman14.timber.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * Created by Christoph Walcher on 03.12.16. + */ + +public class LyricsExtractor { + public static String getLyrics(File file){ + String filename = file.getName(); + String fileending = filename.substring(filename.lastIndexOf('.')+1,filename.length()).toLowerCase(); + try{ + switch(fileending){ + case "mp3": + return getLyricsID3(file); + case "mp4": + case "m4a": + case "aac": + return getLyricsMP4(file); + case "ogg": + case "oga": + return getLyricsVorbis(file); + } + }catch(Exception e){} + return null; + } + + private static int readOgg(byte[] buf, InputStream in, int bytesinpage, int skip) throws IOException { + int toread = skip!=-1?skip:buf.length; + int offset = 0; + while(toread>0){ + if(bytesinpage==0){ + byte magic[] = new byte[4]; + in.read(magic); + if(!Arrays.equals(magic,new byte[]{'O','g','g','S'})){ + in.close(); + throw new IOException(); + } + byte header[] = new byte[23]; + in.read(header); + int count = header[22]& 0xFF; + while(count-->0){ + bytesinpage += in.read(); + } + } + int read = toread; + if(bytesinpage-toread<0)read = bytesinpage; + if(skip != -1) + in.skip(read); + else + in.read(buf, offset, read); + offset += read; + toread -= read; + bytesinpage -= read; + } + return bytesinpage; + } + + private static String getLyricsVorbis(File file) throws Exception{ + FileInputStream in = new FileInputStream(file); + int bytesinpage = 0; + byte buffer[] = new byte[7]; + bytesinpage = readOgg(buffer,in,bytesinpage,-1); + if(!Arrays.equals(buffer, new byte[]{1,'v','o','r','b','i','s'})){ + in.close(); + return null; + } + bytesinpage = readOgg(null,in,bytesinpage, 23); + bytesinpage = readOgg(buffer,in,bytesinpage,-1); + if(!Arrays.equals(buffer, new byte[]{3,'v','o','r','b','i','s'})){ + in.close(); + return null; + } + byte length[] = new byte[4]; + bytesinpage = readOgg(length, in, bytesinpage,-1); + bytesinpage = readOgg(null, in, bytesinpage, byteArrayToInt(length)); + bytesinpage = readOgg(length, in, bytesinpage,-1); + int count = byteArrayToIntLE(length); + while(count-->0){ + bytesinpage = readOgg(length, in, bytesinpage,-1); + int comment_len = byteArrayToIntLE(length); + byte lyrics_tag[] = new byte[]{'L','Y','R','I','C','S','='}; + if(comment_len<=lyrics_tag.length){ + bytesinpage = readOgg(null, in, bytesinpage, comment_len); + continue; + } + byte comment_probe[] = new byte[lyrics_tag.length]; + bytesinpage = readOgg(comment_probe, in, bytesinpage,-1); + if(Arrays.equals(comment_probe,lyrics_tag)){ + byte lyrics[] = new byte[comment_len - lyrics_tag.length]; + readOgg(lyrics, in, bytesinpage,-1); + in.close(); + return new String(lyrics); + }else{ + bytesinpage = readOgg(null, in, bytesinpage, comment_len - lyrics_tag.length); + } + } + in.close(); + return null; + + } + + + private static String getLyricsMP4(File file) throws Exception{ + FileInputStream in = new FileInputStream(file); + + byte head[] = new byte[4]; + in.read(head); + int len = byteArrayToInt(head); + in.read(head); + if (!Arrays.equals(head, new byte[]{'f','t','y','p'})){ + in.close(); + return null; + } + in.skip(len - 8); + final byte path[][] = new byte[][]{{'m','o','o','v'},{'u','d','t','a'},{'m','e','t','a'},{'i','l','s','t'},{(byte) '©','l','y','r'},{'d','a','t','a'}}; + int atom_size = Integer.MAX_VALUE; + outter: + for(byte[] atom: path){ + while(in.available()>0){ + byte buffer[] = new byte[4]; + in.read(buffer); + len = byteArrayToInt(buffer); + if(len==0)continue; + in.read(buffer); + if(len>atom_size){ + in.close(); + return null; + } + if (Arrays.equals(buffer, atom)){ + atom_size = len - 8; + //Found Atom search next atom + continue outter; + }else{ + //Skip Atom + in.skip(len - 8); + atom_size-=len; + } + } + in.close(); + return null; + } + in.skip(8); + byte buffer[] = new byte[atom_size-8]; + in.read(buffer); + in.close(); + return new String(buffer); + } + + + private static String getLyricsID3(File file) throws Exception{ + FileInputStream in = new FileInputStream(file); + byte buffer[] = new byte[4]; + in.read(buffer, 0, 3); + if (!Arrays.equals(buffer, new byte[] { 'I', 'D', '3', 0 })){ + in.close(); + return null; + } + + in.read(buffer, 0, 3); + boolean ext = (buffer[2] & (byte) 0b0100000) != 0; + in.read(buffer); + int len = buffer[3] & 0x7F | (buffer[2] & 0x7F) << 7 | (buffer[1] & 0x7F) << 14 | (buffer[0] & 0x7F) << 21; + if (ext) { + in.read(buffer); len-=4; + int ext_len = byteArrayToInt(buffer); + in.skip(ext_len); len -= ext_len; + + } + while (len > 0) { + byte tag_name[] = new byte[4]; + in.read(tag_name); len-=4; + if(tag_name[0]==0)break; + in.read(buffer); len -=4; + int tag_len = byteArrayToInt(buffer); + in.read(buffer,0,2); len-=2; + if(Arrays.equals(tag_name, new byte[] { 'U', 'S', 'L', 'T' })){ + byte head[] = new byte[4]; + in.read(head); len -= 4; tag_len -= 4; + while(in.read()!=0){ + len--; + tag_len--; + } + if(head[0]==1)in.read(); + byte tag_value[] = new byte[tag_len]; + in.read(tag_value); len -= tag_len; + in.close(); + Charset charset = null; + switch (head[0]){ + case 0: charset = Charset.forName("ISO-8859-1"); + break; + case 1: charset = Charset.forName("UTF-16"); + break; + case 2: charset = Charset.forName("UTF-16BE"); + break; + case 3: charset = Charset.forName("UTF-8"); + break; + default: + return null; + } + return new String(tag_value,charset); + + }else{ + in.skip(tag_len); len -= tag_len; + } + + } + in.close(); + return null; + + } + + private static int byteArrayToInt(byte[] b) { + return b[3] & 0xFF | (b[2] & 0xFF) << 8 | (b[1] & 0xFF) << 16 | (b[0] & 0xFF) << 24; + } + + private static int byteArrayToIntLE(byte[] b) { + return b[0] & 0xFF | (b[1] & 0xFF) << 8 | (b[2] & 0xFF) << 16 | (b[3] & 0xFF) << 24; + } + +} diff --git a/app/src/main/java/com/naman14/timber/utils/LyricsLoader.java b/app/src/main/java/com/naman14/timber/utils/LyricsLoader.java new file mode 100644 index 000000000..f668f3465 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/utils/LyricsLoader.java @@ -0,0 +1,105 @@ +package com.naman14.timber.utils; + +import android.content.Context; + +import com.squareup.okhttp.Cache; +import com.squareup.okhttp.OkHttpClient; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.util.concurrent.TimeUnit; + +import retrofit.Callback; +import retrofit.RequestInterceptor; +import retrofit.RestAdapter; +import retrofit.client.OkClient; +import retrofit.converter.ConversionException; +import retrofit.converter.Converter; +import retrofit.http.GET; +import retrofit.http.Headers; +import retrofit.http.Query; +import retrofit.mime.TypedInput; +import retrofit.mime.TypedOutput; + +/** + * Created by Christoph Walcher on 03.12.16. + */ + +public class LyricsLoader { + private static LyricsLoader instance = null; + private static final String BASE_API_URL = "https://makeitpersonal.co"; + private static final long CACHE_SIZE = 1024 * 1024; + private LyricsRestService service; + + public static LyricsLoader getInstance(Context con) { + if(instance==null)instance = new LyricsLoader(con); + return instance; + } + + private LyricsLoader(Context con){ + final OkHttpClient okHttpClient = new OkHttpClient(); + + okHttpClient.setCache(new Cache(con.getApplicationContext().getCacheDir(), + CACHE_SIZE)); + okHttpClient.setConnectTimeout(20, TimeUnit.SECONDS); + + RequestInterceptor interceptor = new RequestInterceptor() { + @Override + public void intercept(RequestFacade request) { + //7-days cache + request.addHeader("Cache-Control", String.format("max-age=%d,max-stale=%d", Integer.valueOf(60 * 60 * 24 * 7), Integer.valueOf(31536000))); + } + }; + + RestAdapter.Builder builder = new RestAdapter.Builder() + .setEndpoint(BASE_API_URL) + .setRequestInterceptor(interceptor) + .setConverter(new Converter() { + @Override + public Object fromBody(TypedInput arg0, Type arg1) + throws ConversionException { + + try { + BufferedReader br = null; + StringBuilder sb = new StringBuilder(); + + String line; + + br = new BufferedReader(new InputStreamReader(arg0.in())); + while ((line = br.readLine()) != null) { + sb.append(line); + sb.append('\n'); + } + return sb.toString(); + + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public TypedOutput toBody(Object arg0) { + return null; + } + }) + .setClient(new OkClient(okHttpClient)); + + service = builder + .build() + .create(LyricsRestService.class); + } + + public void getLyrics(String artist, String title, Callback callback){ + service.getLyrics(artist,title,callback); + } + + private interface LyricsRestService { + @Headers("Cache-Control: public") + @GET("/lyrics") + void getLyrics(@Query("artist") String artist, @Query("title") String title, Callback callback); + } + +} diff --git a/app/src/main/java/com/naman14/timber/utils/NavigationUtils.java b/app/src/main/java/com/naman14/timber/utils/NavigationUtils.java index fe076f8e6..367403da6 100644 --- a/app/src/main/java/com/naman14/timber/utils/NavigationUtils.java +++ b/app/src/main/java/com/naman14/timber/utils/NavigationUtils.java @@ -20,11 +20,10 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; -import android.transition.Transition; -import android.transition.TransitionInflater; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.appcompat.app.AppCompatActivity; + import android.util.Pair; import android.view.View; import android.widget.Toast; @@ -41,6 +40,8 @@ import com.naman14.timber.nowplaying.Timber2; import com.naman14.timber.nowplaying.Timber3; import com.naman14.timber.nowplaying.Timber4; +import com.naman14.timber.nowplaying.Timber5; +import com.naman14.timber.nowplaying.Timber6; import java.util.ArrayList; @@ -52,16 +53,10 @@ public static void navigateToAlbum(Activity context, long albumID, Pair transitionViews) { final Intent intent = new Intent(context, PlaylistDetailActivity.class); - if (!PreferencesUtility.getInstance(context).getSystemAnimations()) { - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - } intent.setAction(action); intent.putExtra(Constants.PLAYLIST_ID, playlistID); intent.putExtra(Constants.PLAYLIST_FOREGROUND_COLOR, foregroundcolor); intent.putExtra(Constants.ALBUM_ID, firstAlbumID); intent.putExtra(Constants.PLAYLIST_NAME, playlistName); + intent.putExtra(Constants.ACTIVITY_TRANSITION, transitionViews != null); - if (TimberUtils.isLollipop() && PreferencesUtility.getInstance(context).getAnimations()) { - ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(MainActivity.getInstance(), transitionViews.get(0), transitionViews.get(1), transitionViews.get(2)); - context.startActivity(intent, options.toBundle()); + if (transitionViews != null && TimberUtils.isLollipop()) { + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(context, transitionViews.get(0), transitionViews.get(1), transitionViews.get(2)); + context.startActivityForResult(intent, Constants.ACTION_DELETE_PLAYLIST, options.toBundle()); } else { - context.startActivity(intent); + context.startActivityForResult(intent, Constants.ACTION_DELETE_PLAYLIST); } } @@ -168,9 +155,6 @@ public static void navigateToEqualizer(Activity context) { public static Intent getNavigateToStyleSelectorIntent(Activity context, String what) { final Intent intent = new Intent(context, SettingsActivity.class); - if (!PreferencesUtility.getInstance(context).getSystemAnimations()) { - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - } intent.setAction(Constants.SETTINGS_STYLE_SELECTOR); intent.putExtra(Constants.SETTINGS_STYLE_SELECTOR_WHAT, what); return intent; @@ -186,10 +170,34 @@ public static Fragment getFragmentForNowplayingID(String fragmentID) { return new Timber3(); case Constants.TIMBER4: return new Timber4(); + case Constants.TIMBER5: + return new Timber5(); + case Constants.TIMBER6: + return new Timber6(); default: return new Timber1(); } } + public static int getIntForCurrentNowplaying(String nowPlaying) { + switch (nowPlaying) { + case Constants.TIMBER1: + return 0; + case Constants.TIMBER2: + return 1; + case Constants.TIMBER3: + return 2; + case Constants.TIMBER4: + return 3; + case Constants.TIMBER5: + return 4; + case Constants.TIMBER6: + return 5; + default: + return 2; + } + + } + } diff --git a/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java b/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java index d079c0fb2..1686e5604 100644 --- a/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java +++ b/app/src/main/java/com/naman14/timber/utils/PreferencesUtility.java @@ -15,11 +15,18 @@ package com.naman14.timber.utils; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.os.AsyncTask; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Bundle; +import android.os.Environment; import android.preference.PreferenceManager; +import com.naman14.timber.MusicPlayer; +import com.naman14.timber.MusicService; + public final class PreferencesUtility { public static final String ARTIST_SORT_ORDER = "artist_sort_order"; @@ -33,16 +40,34 @@ public final class PreferencesUtility { private static final String TOGGLE_SYSTEM_ANIMATIONS = "toggle_system_animations"; private static final String TOGGLE_ARTIST_GRID = "toggle_artist_grid"; private static final String TOGGLE_ALBUM_GRID = "toggle_album_grid"; + private static final String TOGGLE_PLAYLIST_VIEW = "toggle_playlist_view"; + private static final String TOGGLE_SHOW_AUTO_PLAYLIST = "toggle_show_auto_playlist"; + private static final String LAST_FOLDER = "last_folder"; + private static final String TOGGLE_HEADPHONE_PAUSE = "toggle_headphone_pause"; private static final String THEME_PREFERNCE = "theme_preference"; private static final String START_PAGE_INDEX = "start_page_index"; private static final String START_PAGE_PREFERENCE_LASTOPENED = "start_page_preference_latopened"; private static final String NOW_PLAYNG_THEME_VALUE = "now_playing_theme_value"; + private static final String TOGGLE_XPOSED_TRACKSELECTOR = "toggle_xposed_trackselector"; + public static final String LAST_ADDED_CUTOFF = "last_added_cutoff"; + public static final String GESTURES = "gestures"; + + public static final String FULL_UNLOCKED = "full_version_unlocked"; + + private static final String SHOW_LOCKSCREEN_ALBUMART = "show_albumart_lockscreen"; + private static final String ARTIST_ALBUM_IMAGE = "artist_album_image"; + private static final String ARTIST_ALBUM_IMAGE_MOBILE = "artist_album_image_mobile"; + private static final String ALWAYS_LOAD_ALBUM_IMAGES_LASTFM = "always_load_album_images_lastfm"; + private static PreferencesUtility sInstance; private static SharedPreferences mPreferences; + private static Context context; + private ConnectivityManager connManager = null; public PreferencesUtility(final Context context) { + this.context = context; mPreferences = PreferenceManager.getDefaultSharedPreferences(context); } @@ -58,29 +83,14 @@ public void setOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener mPreferences.registerOnSharedPreferenceChangeListener(listener); } - public boolean getAnimations() { - return mPreferences.getBoolean(TOGGLE_ANIMATIONS, true); - } - - public boolean getSystemAnimations() { - return mPreferences.getBoolean(TOGGLE_SYSTEM_ANIMATIONS, true); - } - public boolean isArtistsInGrid() { return mPreferences.getBoolean(TOGGLE_ARTIST_GRID, true); } public void setArtistsInGrid(final boolean b) { - new AsyncTask() { - @Override - protected Void doInBackground(final Void... unused) { - final SharedPreferences.Editor editor = mPreferences.edit(); - editor.putBoolean(TOGGLE_ARTIST_GRID, b); - editor.apply(); - return null; - } - }.execute(); - + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(TOGGLE_ARTIST_GRID, b); + editor.apply(); } public boolean isAlbumsInGrid() { @@ -88,16 +98,9 @@ public boolean isAlbumsInGrid() { } public void setAlbumsInGrid(final boolean b) { - new AsyncTask() { - @Override - protected Void doInBackground(final Void... unused) { - final SharedPreferences.Editor editor = mPreferences.edit(); - editor.putBoolean(TOGGLE_ALBUM_GRID, b); - editor.apply(); - return null; - } - }.execute(); - + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(TOGGLE_ALBUM_GRID, b); + editor.apply(); } public boolean pauseEnabledOnDetach() { @@ -113,15 +116,9 @@ public int getStartPageIndex() { } public void setStartPageIndex(final int index) { - new AsyncTask() { - @Override - protected Void doInBackground(final Void... unused) { - final SharedPreferences.Editor editor = mPreferences.edit(); - editor.putInt(START_PAGE_INDEX, index); - editor.apply(); - return null; - } - }.execute(); + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(START_PAGE_INDEX, index); + editor.apply(); } public void setLastOpenedAsStartPagePreference(boolean preference) { @@ -135,16 +132,9 @@ public boolean lastOpenedIsStartPagePreference() { } private void setSortOrder(final String key, final String value) { - new AsyncTask() { - @Override - protected Void doInBackground(final Void... unused) { - final SharedPreferences.Editor editor = mPreferences.edit(); - editor.putString(key, value); - editor.apply(); - - return null; - } - }.execute(); + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(key, value); + editor.apply(); } public final String getArtistSortOrder() { @@ -207,4 +197,90 @@ public void setNowPlayingThemeChanged(final boolean value) { editor.putBoolean(NOW_PLAYNG_THEME_VALUE, value); editor.apply(); } -} \ No newline at end of file + + public boolean getXPosedTrackselectorEnabled() { + return mPreferences.getBoolean(TOGGLE_XPOSED_TRACKSELECTOR, false); + } + + public int getPlaylistView() { + return mPreferences.getInt(TOGGLE_PLAYLIST_VIEW ,0); + } + + public void setPlaylistView(final int i) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(TOGGLE_PLAYLIST_VIEW, i); + editor.apply(); + } + + public boolean showAutoPlaylist() { + return mPreferences.getBoolean(TOGGLE_SHOW_AUTO_PLAYLIST, true); + } + + public void setToggleShowAutoPlaylist(final boolean b) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(TOGGLE_SHOW_AUTO_PLAYLIST, b); + editor.apply(); + } + + /** @parm lastAddedMillis timestamp in millis used as a cutoff for last added playlist */ + public void setLastAddedCutoff(long lastAddedMillis) { + mPreferences.edit().putLong(LAST_ADDED_CUTOFF, lastAddedMillis).apply(); + } + + public long getLastAddedCutoff() { + return mPreferences.getLong(LAST_ADDED_CUTOFF, 0L); + } + + public boolean isGesturesEnabled() { + return mPreferences.getBoolean(GESTURES, true); + } + + public void storeLastFolder(String path) { + SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(LAST_FOLDER, path); + editor.apply(); + } + + public String getLastFolder() { + return mPreferences.getString(LAST_FOLDER, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath()); + } + + public boolean fullUnlocked() { + return mPreferences.getBoolean(FULL_UNLOCKED, false); + } + + public void setFullUnlocked(final boolean b) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(FULL_UNLOCKED, b); + editor.apply(); + } + + public boolean getSetAlbumartLockscreen() { + return mPreferences.getBoolean(SHOW_LOCKSCREEN_ALBUMART, true); + } + + public void updateService(Bundle extras) { + if(!MusicPlayer.isPlaybackServiceConnected())return; + final Intent intent = new Intent(context, MusicService.class); + intent.setAction(MusicService.UPDATE_PREFERENCES); + intent.putExtras(extras); + context.startService(intent); + } + + public boolean loadArtistAndAlbumImages() { + if (mPreferences.getBoolean(ARTIST_ALBUM_IMAGE, true)) { + if (!mPreferences.getBoolean(ARTIST_ALBUM_IMAGE_MOBILE, true)) { + if (connManager == null) connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo ni = connManager.getActiveNetworkInfo(); + return ni != null && ni.getType() == ConnectivityManager.TYPE_WIFI; + } + return true; + } + return false; + } + + public boolean alwaysLoadAlbumImagesFromLastfm() { + return mPreferences.getBoolean(ALWAYS_LOAD_ALBUM_IMAGES_LASTFM, false); + } +} + diff --git a/app/src/main/java/com/naman14/timber/utils/SlideTrackSwitcher.java b/app/src/main/java/com/naman14/timber/utils/SlideTrackSwitcher.java new file mode 100644 index 000000000..86ffbd8b2 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/utils/SlideTrackSwitcher.java @@ -0,0 +1,105 @@ +package com.naman14.timber.utils; + +import androidx.annotation.NonNull; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import com.naman14.timber.MusicPlayer; + +/** + * Created by nv95 on 02.11.16. + */ + +public class SlideTrackSwitcher implements View.OnTouchListener { + + private static final int SWIPE_THRESHOLD = 200; + private static final int SWIPE_VELOCITY_THRESHOLD = 100; + + private GestureDetector mDetector; + private View mView; + + + public SlideTrackSwitcher() { + } + + public void attach(@NonNull View v) { + mView = v; + mDetector = new GestureDetector(v.getContext(), new SwipeListener()); + v.setOnTouchListener(this); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + return mDetector.onTouchEvent(event); + } + + private class SwipeListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + boolean result = false; + try { + float diffY = e2.getY() - e1.getY(); + float diffX = e2.getX() - e1.getX(); + if (Math.abs(diffX) > Math.abs(diffY)) { + if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { + if (diffX > 0) { + onSwipeRight(); + } else { + onSwipeLeft(); + } + } + result = true; + } + else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) { + if (diffY > 0) { + onSwipeBottom(); + } else { + onSwipeTop(); + } + } + result = true; + + } catch (Exception exception) { + exception.printStackTrace(); + } + return result; + } + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + MusicPlayer.playOrPause(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + onClick(); + return super.onSingleTapConfirmed(e); + } + } + + public void onSwipeRight() { + MusicPlayer.previous(mView.getContext(), true); + } + + public void onSwipeLeft() { + MusicPlayer.next(); + } + + public void onSwipeTop() { + } + + public void onSwipeBottom() { + } + + public void onClick() { + + } +} diff --git a/app/src/main/java/com/naman14/timber/utils/TimberUtils.java b/app/src/main/java/com/naman14/timber/utils/TimberUtils.java index 97ac1dea4..5650db5ee 100644 --- a/app/src/main/java/com/naman14/timber/utils/TimberUtils.java +++ b/app/src/main/java/com/naman14/timber/utils/TimberUtils.java @@ -15,27 +15,49 @@ package com.naman14.timber.utils; import android.app.Activity; +import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Color; +import android.media.MediaMetadataRetriever; import android.media.audiofx.AudioEffect; import android.net.Uri; import android.os.Build; import android.provider.BaseColumns; import android.provider.MediaStore; +import androidx.annotation.NonNull; + +import android.util.Log; import android.util.TypedValue; +import android.widget.Toast; +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; import com.naman14.timber.MusicPlayer; import com.naman14.timber.R; +import com.naman14.timber.adapters.BaseQueueAdapter; +import com.naman14.timber.adapters.BaseSongAdapter; +import com.naman14.timber.provider.RecentStore; +import com.naman14.timber.provider.SongPlayCount; + +import java.io.File; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Collections; +import java.util.List; public class TimberUtils { public static final String MUSIC_ONLY_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"; + public static boolean isOreo() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + public static boolean isMarshmallow() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; } @@ -57,8 +79,14 @@ public static boolean isJellyBeanMR1() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; } - public static Uri getAlbumArtUri(long paramInt) { - return ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), paramInt); + public static Uri getAlbumArtUri(long albumId) { + return ContentUris.withAppendedId(Uri.parse("content://media/external/audio/albumart"), albumId); + } + public static String getAlbumArtForFile(String filePath) { + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + mmr.setDataSource(filePath); + + return mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); } public static final String makeCombinedString(final Context context, final String first, @@ -180,5 +208,207 @@ public static PlaylistType getTypeById(long id) { } } + public static void removeFromPlaylist(final Context context, final long id, + final long playlistId) { + final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId); + final ContentResolver resolver = context.getContentResolver(); + resolver.delete(uri, MediaStore.Audio.Playlists.Members.AUDIO_ID + " = ? ", new String[]{ + Long.toString(id) + }); + } + + public static void clearTopTracks(Context context) { + SongPlayCount.getInstance(context).deleteAll(); + } + + public static void clearRecent(Context context) { + RecentStore.getInstance(context).deleteAll(); + } + + public static void clearLastAdded(Context context) { + PreferencesUtility.getInstance(context) + .setLastAddedCutoff(System.currentTimeMillis()); + } + + public static void showDeleteDialog(final Context context, final String name, final long[] list, final BaseSongAdapter adapter, final int pos) { + + new MaterialDialog.Builder(context) + .title("Delete song?") + .content("Are you sure you want to delete " + name + " ?") + .positiveText("Delete") + .negativeText("Cancel") + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + TimberUtils.deleteTracks(context, list); + adapter.removeSongAt(pos); + adapter.notifyItemRemoved(pos); + adapter.notifyItemRangeChanged(pos, adapter.getItemCount()); + } + }) + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + dialog.dismiss(); + } + }) + .show(); + } + + public static void showDeleteDialog(final Context context, final String name, final long[] list, final BaseQueueAdapter qAdapter, final int pos) { + + new MaterialDialog.Builder(context) + .title("Delete song?") + .content("Are you sure you want to delete " + name + " ?") + .positiveText("Delete") + .negativeText("Cancel") + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + TimberUtils.deleteTracks(context, list); + qAdapter.removeSongAt(pos); + qAdapter.notifyItemRemoved(pos); + qAdapter.notifyItemRangeChanged(pos, qAdapter.getItemCount()); + } + }) + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + dialog.dismiss(); + } + }) + .show(); + } + + + public static void deleteTracks(final Context context, final long[] list) { + final String[] projection = new String[]{ + BaseColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.Audio.AudioColumns.ALBUM_ID + }; + final StringBuilder selection = new StringBuilder(); + selection.append(BaseColumns._ID + " IN ("); + for (int i = 0; i < list.length; i++) { + selection.append(list[i]); + if (i < list.length - 1) { + selection.append(","); + } + } + selection.append(")"); + final Cursor c = context.getContentResolver().query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), + null, null); + if (c != null) { + // Step 1: Remove selected tracks from the current playlist, as well + // as from the album art cache + c.moveToFirst(); + while (!c.isAfterLast()) { + // Remove from current playlist + final long id = c.getLong(0); + MusicPlayer.removeTrack(id); + // Remove the track from the play count + SongPlayCount.getInstance(context).removeItem(id); + // Remove any items in the recents database + RecentStore.getInstance(context).removeItem(id); + c.moveToNext(); + } + + // Step 2: Remove selected tracks from the database + context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + selection.toString(), null); + + // Step 3: Remove files from card + c.moveToFirst(); + while (!c.isAfterLast()) { + final String name = c.getString(1); + final File f = new File(name); + try { // File.delete can throw a security exception + if (!f.delete()) { + // I'm not sure if we'd ever get here (deletion would + // have to fail, but no exception thrown) + Log.e("MusicUtils", "Failed to delete file " + name); + } + c.moveToNext(); + } catch (final SecurityException ex) { + c.moveToNext(); + } + } + c.close(); + } + + final String message = makeLabel(context, R.plurals.NNNtracksdeleted, list.length); + + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + MusicPlayer.refresh(); + } + + public static void shareTrack(final Context context, long id) { + + try { + Intent share = new Intent(Intent.ACTION_SEND); + share.setType("audio/*"); + share.putExtra(Intent.EXTRA_STREAM, getSongUri(context, id)); + context.startActivity(Intent.createChooser(share, "Share")); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Uri getSongUri(Context context, long id) { + final String[] projection = new String[]{ + BaseColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.Audio.AudioColumns.ALBUM_ID + }; + final StringBuilder selection = new StringBuilder(); + selection.append(BaseColumns._ID + " IN ("); + selection.append(id); + selection.append(")"); + final Cursor c = context.getContentResolver().query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), + null, null); + + if (c == null) { + return null; + } + c.moveToFirst(); + + + try { + + Uri uri = Uri.parse(c.getString(1)); + c.close(); + + return uri; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static String getIPAddress(boolean useIPv4) { + try { + List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface intf : interfaces) { + List addrs = Collections.list(intf.getInetAddresses()); + for (InetAddress addr : addrs) { + if (!addr.isLoopbackAddress()) { + String sAddr = addr.getHostAddress(); + //boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr); + boolean isIPv4 = sAddr.indexOf(':')<0; + + if (useIPv4) { + if (isIPv4) + return sAddr; + } else { + if (!isIPv4) { + int delim = sAddr.indexOf('%'); // drop ip6 zone suffix + return delim<0 ? sAddr.toUpperCase() : sAddr.substring(0, delim).toUpperCase(); + } + } + } + } + } + } catch (Exception ex) { } + return ""; + } } diff --git a/app/src/main/java/com/naman14/timber/widgets/BaseRecyclerView.java b/app/src/main/java/com/naman14/timber/widgets/BaseRecyclerView.java new file mode 100644 index 000000000..a5576251b --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/BaseRecyclerView.java @@ -0,0 +1,72 @@ +package com.naman14.timber.widgets; + +import android.content.Context; + +import androidx.recyclerview.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import com.afollestad.appthemeengine.Config; +import com.naman14.timber.utils.Helpers; + +import net.steamcrafted.materialiconlib.MaterialDrawableBuilder; + +public class BaseRecyclerView extends RecyclerView { + + private View emptyView; + + private AdapterDataObserver emptyObserver = new AdapterDataObserver() { + + @Override + public void onChanged() { + Adapter adapter = getAdapter(); + if(adapter != null && emptyView != null) { + if(adapter.getItemCount() == 0) { + emptyView.setVisibility(View.VISIBLE); + BaseRecyclerView.this.setVisibility(View.GONE); + } + else { + emptyView.setVisibility(View.GONE); + BaseRecyclerView.this.setVisibility(View.VISIBLE); + } + } + + } + }; + + public BaseRecyclerView(Context context) { + super(context); + } + + public BaseRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BaseRecyclerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setAdapter(Adapter adapter) { + super.setAdapter(adapter); + + if(adapter != null) { + adapter.registerAdapterDataObserver(emptyObserver); + } + + emptyObserver.onChanged(); + } + + public void setEmptyView(Context context, View emptyView, String text) { + this.emptyView = emptyView; + ((TextView) emptyView).setText(text); + + MaterialDrawableBuilder builder = MaterialDrawableBuilder.with(context) + .setIcon(MaterialDrawableBuilder.IconValue.MUSIC_NOTE) + .setColor(Config.textColorPrimary(context, Helpers.getATEKey(context))) + .setSizeDp(30); + + ((TextView) emptyView).setCompoundDrawables(null, builder.build(), null, null); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/naman14/timber/widgets/CircleImageView.java b/app/src/main/java/com/naman14/timber/widgets/CircleImageView.java index 710b44e22..30ba8e25e 100644 --- a/app/src/main/java/com/naman14/timber/widgets/CircleImageView.java +++ b/app/src/main/java/com/naman14/timber/widgets/CircleImageView.java @@ -15,9 +15,9 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; -import android.support.v4.content.ContextCompat; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.core.content.ContextCompat; import android.util.AttributeSet; import android.widget.ImageView; diff --git a/app/src/main/java/com/naman14/timber/widgets/DividerItemDecoration.java b/app/src/main/java/com/naman14/timber/widgets/DividerItemDecoration.java index 3f9856aac..80323eebb 100644 --- a/app/src/main/java/com/naman14/timber/widgets/DividerItemDecoration.java +++ b/app/src/main/java/com/naman14/timber/widgets/DividerItemDecoration.java @@ -5,9 +5,9 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.view.View; import com.naman14.timber.R; diff --git a/app/src/main/java/com/naman14/timber/widgets/DragSortRecycler.java b/app/src/main/java/com/naman14/timber/widgets/DragSortRecycler.java index a851e1fa8..0db007d48 100644 --- a/app/src/main/java/com/naman14/timber/widgets/DragSortRecycler.java +++ b/app/src/main/java/com/naman14/timber/widgets/DragSortRecycler.java @@ -5,8 +5,8 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.MotionEvent; import android.view.View; diff --git a/app/src/main/java/com/naman14/timber/widgets/FastScroller.java b/app/src/main/java/com/naman14/timber/widgets/FastScroller.java index 7bced1119..cc8e98e20 100644 --- a/app/src/main/java/com/naman14/timber/widgets/FastScroller.java +++ b/app/src/main/java/com/naman14/timber/widgets/FastScroller.java @@ -5,9 +5,9 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -17,7 +17,7 @@ import com.naman14.timber.R; -import static android.support.v7.widget.RecyclerView.OnScrollListener; +import static androidx.recyclerview.widget.RecyclerView.OnScrollListener; public class FastScroller extends LinearLayout { private static final int BUBBLE_ANIMATION_DURATION = 100; diff --git a/app/src/main/java/com/naman14/timber/widgets/MultiViewPager.java b/app/src/main/java/com/naman14/timber/widgets/MultiViewPager.java index a7db8d211..e1a75befc 100644 --- a/app/src/main/java/com/naman14/timber/widgets/MultiViewPager.java +++ b/app/src/main/java/com/naman14/timber/widgets/MultiViewPager.java @@ -3,7 +3,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Point; -import android.support.v4.view.ViewPager; +import androidx.viewpager.widget.ViewPager; import android.util.AttributeSet; import android.view.View; diff --git a/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java b/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java index 8128d0f0c..168bdc95e 100644 --- a/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java +++ b/app/src/main/java/com/naman14/timber/widgets/MusicVisualizer.java @@ -21,8 +21,8 @@ public class MusicVisualizer extends View { @Override public void run() { - //run every 150 ms - postDelayed(this, 150); + //run every 100 ms + postDelayed(this, 120); invalidate(); } @@ -48,9 +48,9 @@ protected void onDraw(Canvas canvas) { //set paint style, Style.FILL will fill the color, Style.STROKE will stroke the color paint.setStyle(Paint.Style.FILL); - canvas.drawRect(getDimensionInPixel(0), getHeight() - random.nextInt((int) (getHeight() / 1.5f)), getDimensionInPixel(7), getHeight(), paint); - canvas.drawRect(getDimensionInPixel(10), getHeight() - random.nextInt((int) (getHeight() / 1.5f)), getDimensionInPixel(17), getHeight(), paint); - canvas.drawRect(getDimensionInPixel(20), getHeight() - random.nextInt((int) (getHeight() / 1.5f)), getDimensionInPixel(27), getHeight(), paint); + canvas.drawRect(getDimensionInPixel(0), getHeight() - (40 + random.nextInt((int) (getHeight() / 1.5f) - 25)), getDimensionInPixel(7), getHeight() - 15, paint); + canvas.drawRect(getDimensionInPixel(10), getHeight() - (40 + random.nextInt((int) (getHeight() / 1.5f) - 25)), getDimensionInPixel(17), getHeight() -15, paint); + canvas.drawRect(getDimensionInPixel(20), getHeight() - (40 + random.nextInt((int) (getHeight() / 1.5f) - 25)), getDimensionInPixel(27), getHeight() -15, paint); } public void setColor(int color) { diff --git a/app/src/main/java/com/naman14/timber/widgets/PlayPauseButton.java b/app/src/main/java/com/naman14/timber/widgets/PlayPauseButton.java index 13043df32..a6796965a 100644 --- a/app/src/main/java/com/naman14/timber/widgets/PlayPauseButton.java +++ b/app/src/main/java/com/naman14/timber/widgets/PlayPauseButton.java @@ -9,7 +9,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.util.AttributeSet; import android.view.View; diff --git a/app/src/main/java/com/naman14/timber/widgets/PlayPauseDrawable.java b/app/src/main/java/com/naman14/timber/widgets/PlayPauseDrawable.java index 56ff26bc1..a4b293772 100644 --- a/app/src/main/java/com/naman14/timber/widgets/PlayPauseDrawable.java +++ b/app/src/main/java/com/naman14/timber/widgets/PlayPauseDrawable.java @@ -39,7 +39,7 @@ import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.util.Log; import android.util.Property; import android.view.animation.DecelerateInterpolator; diff --git a/app/src/main/java/com/naman14/timber/widgets/PopupImageView.java b/app/src/main/java/com/naman14/timber/widgets/PopupImageView.java new file mode 100644 index 000000000..48fa7daee --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/PopupImageView.java @@ -0,0 +1,44 @@ +package com.naman14.timber.widgets; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Color; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.afollestad.appthemeengine.util.TintHelper; + +/** + * Created by naman on 29/10/16. + */ +public class PopupImageView extends ImageView { + + public PopupImageView(Context context) { + super(context); + tint(); + } + + public PopupImageView(Context context, AttributeSet attrs) { + super(context, attrs); + tint(); + } + + public PopupImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + tint(); + } + + @TargetApi(21) + public PopupImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + tint(); + } + + private void tint() { + if (PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("dark_theme", false)) { + TintHelper.setTint(this, Color.parseColor("#eeeeee")); + } else TintHelper.setTint(this, Color.parseColor("#434343")); + } + +} diff --git a/app/src/main/java/com/naman14/timber/widgets/desktop/BaseWidget.java b/app/src/main/java/com/naman14/timber/widgets/desktop/BaseWidget.java new file mode 100644 index 000000000..6bb5569dd --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/desktop/BaseWidget.java @@ -0,0 +1,56 @@ +package com.naman14.timber.widgets.desktop; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import androidx.annotation.LayoutRes; +import android.widget.RemoteViews; + +import com.naman14.timber.MusicService; + +/** + * Created by nv95 on 02.11.16. + */ + +public abstract class BaseWidget extends AppWidgetProvider { + + protected static final int REQUEST_NEXT = 1; + protected static final int REQUEST_PREV = 2; + protected static final int REQUEST_PLAYPAUSE = 3; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + onUpdate(context, appWidgetManager, appWidgetIds, null); + } + + private void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,Bundle extras){ + ComponentName serviceName = new ComponentName(context, MusicService.class); + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutRes()); + try { + onViewsUpdate(context, remoteViews, serviceName, extras); + appWidgetManager.updateAppWidget(appWidgetIds, remoteViews); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null && action.startsWith("com.naman14.timber.")) { + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + ComponentName thisAppWidget = new ComponentName(context.getPackageName(), this.getClass().getName()); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget); + onUpdate(context, appWidgetManager, appWidgetIds, intent.getExtras()); + } else { + super.onReceive(context, intent); + } + } + + abstract void onViewsUpdate(Context context, RemoteViews remoteViews, ComponentName serviceName, Bundle extras); + + abstract @LayoutRes int getLayoutRes(); +} diff --git a/app/src/main/java/com/naman14/timber/widgets/desktop/SmallWidget.java b/app/src/main/java/com/naman14/timber/widgets/desktop/SmallWidget.java new file mode 100644 index 000000000..d7433aaa2 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/desktop/SmallWidget.java @@ -0,0 +1,74 @@ +package com.naman14.timber.widgets.desktop; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.widget.RemoteViews; + +import com.naman14.timber.MusicService; +import com.naman14.timber.R; +import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.TimberUtils; +import com.nostra13.universalimageloader.core.ImageLoader; + +/** + * Created by nv95 on 02.11.16. + */ + +public class SmallWidget extends BaseWidget { + + @Override + int getLayoutRes() { + return R.layout.widget_small; + } + + @Override + void onViewsUpdate(Context context, RemoteViews remoteViews, ComponentName serviceName, Bundle extras) { + remoteViews.setOnClickPendingIntent(R.id.image_next, PendingIntent.getService( + context, + REQUEST_NEXT, + new Intent(context, MusicService.class) + .setAction(MusicService.NEXT_ACTION) + .setComponent(serviceName), + 0 + )); + remoteViews.setOnClickPendingIntent(R.id.image_playpause, PendingIntent.getService( + context, + REQUEST_PLAYPAUSE, + new Intent(context, MusicService.class) + .setAction(MusicService.TOGGLEPAUSE_ACTION) + .setComponent(serviceName), + 0 + )); + if (extras != null) { + String t = extras.getString("track"); + if (t != null) { + remoteViews.setTextViewText(R.id.textView_title, t); + } + t = extras.getString("artist"); + if (t != null) { + remoteViews.setTextViewText(R.id.textView_subtitle, t); + } + remoteViews.setImageViewResource(R.id.image_playpause, + extras.getBoolean("playing") ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp); + long albumId = extras.getLong("albumid"); + if (albumId != -1) { + Bitmap artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(albumId).toString()); + if (artwork != null) { + remoteViews.setImageViewBitmap(R.id.imageView_cover, artwork); + } else { + remoteViews.setImageViewResource(R.id.imageView_cover, R.drawable.ic_empty_music2); + } + } + } + remoteViews.setOnClickPendingIntent(R.id.textView_title, PendingIntent.getActivity( + context, + 0, + NavigationUtils.getNowPlayingIntent(context), + PendingIntent.FLAG_UPDATE_CURRENT + )); + } +} diff --git a/app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java b/app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java new file mode 100644 index 000000000..c4197c169 --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/desktop/StandardWidget.java @@ -0,0 +1,90 @@ +package com.naman14.timber.widgets.desktop; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.RemoteViews; + +import com.naman14.timber.MusicService; +import com.naman14.timber.R; +import com.naman14.timber.utils.NavigationUtils; +import com.naman14.timber.utils.TimberUtils; +import com.nostra13.universalimageloader.core.ImageLoader; + +/** + * Created by nv95 on 08.07.16. + */ + +public class StandardWidget extends BaseWidget { + + @Override + int getLayoutRes() { + return R.layout.widget_standard; + } + + @Override + void onViewsUpdate(Context context, RemoteViews remoteViews, ComponentName serviceName, Bundle extras) { + remoteViews.setOnClickPendingIntent(R.id.image_next, PendingIntent.getService( + context, + REQUEST_NEXT, + new Intent(context, MusicService.class) + .setAction(MusicService.NEXT_ACTION) + .setComponent(serviceName), + 0 + )); + remoteViews.setOnClickPendingIntent(R.id.image_prev, PendingIntent.getService( + context, + REQUEST_PREV, + new Intent(context, MusicService.class) + .setAction(MusicService.PREVIOUS_ACTION) + .setComponent(serviceName), + 0 + )); + remoteViews.setOnClickPendingIntent(R.id.image_playpause, PendingIntent.getService( + context, + REQUEST_PLAYPAUSE, + new Intent(context, MusicService.class) + .setAction(MusicService.TOGGLEPAUSE_ACTION) + .setComponent(serviceName), + 0 + )); + + if (extras != null) { + String t = extras.getString("track"); + if (t != null) { + remoteViews.setTextViewText(R.id.textView_title, t); + } + t = extras.getString("artist"); + ; + if (t != null) { + String album = extras.getString("album"); + ; + if (!TextUtils.isEmpty(album)) { + t += " - " + album; + } + remoteViews.setTextViewText(R.id.textView_subtitle, t); + } + remoteViews.setImageViewResource(R.id.image_playpause, + extras.getBoolean("playing") ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_white_36dp); + long albumId = extras.getLong("albumid"); + if (albumId != -1) { + Bitmap artwork = ImageLoader.getInstance().loadImageSync(TimberUtils.getAlbumArtUri(albumId).toString()); + if (artwork != null) { + remoteViews.setImageViewBitmap(R.id.imageView_cover, artwork); + } else { + remoteViews.setImageViewResource(R.id.imageView_cover, R.drawable.ic_empty_music2); + } + } + } + remoteViews.setOnClickPendingIntent(R.id.imageView_cover, PendingIntent.getActivity( + context, + 0, + NavigationUtils.getNowPlayingIntent(context), + PendingIntent.FLAG_UPDATE_CURRENT + )); + } +} diff --git a/app/src/main/java/com/naman14/timber/widgets/desktop/WhiteWidget.java b/app/src/main/java/com/naman14/timber/widgets/desktop/WhiteWidget.java new file mode 100644 index 000000000..705fa089c --- /dev/null +++ b/app/src/main/java/com/naman14/timber/widgets/desktop/WhiteWidget.java @@ -0,0 +1,15 @@ +package com.naman14.timber.widgets.desktop; + +import com.naman14.timber.R; + +/** + * Created by nv95 on 11.11.16. + */ + +public class WhiteWidget extends StandardWidget { + + @Override + int getLayoutRes() { + return R.layout.widget_white; + } +} diff --git a/app/src/main/res/anim/design_fab_out.xml b/app/src/main/res/anim/design_fab_out.xml new file mode 100644 index 000000000..ba76fb597 --- /dev/null +++ b/app/src/main/res/anim/design_fab_out.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_lock_outline_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_lock_outline_white_24dp.png new file mode 100644 index 000000000..a6b80507b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_lock_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png new file mode 100644 index 000000000..22acc5500 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png new file mode 100644 index 000000000..f3b7a4c4d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_more_vert_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/payment_black.png b/app/src/main/res/drawable-hdpi/payment_black.png new file mode 100644 index 000000000..a7b898237 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/payment_black.png differ diff --git a/app/src/main/res/drawable-hdpi/payment_white.png b/app/src/main/res/drawable-hdpi/payment_white.png new file mode 100644 index 000000000..ad4b59d02 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/payment_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_lock_outline_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_lock_outline_white_24dp.png new file mode 100644 index 000000000..5700e9b3c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_lock_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png new file mode 100644 index 000000000..0e4f2f6ea Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png new file mode 100644 index 000000000..e2fd14954 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_more_vert_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/payment_black.png b/app/src/main/res/drawable-mdpi/payment_black.png new file mode 100644 index 000000000..84fcee697 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/payment_black.png differ diff --git a/app/src/main/res/drawable-mdpi/payment_white.png b/app/src/main/res/drawable-mdpi/payment_white.png new file mode 100644 index 000000000..e89f4b654 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/payment_white.png differ diff --git a/app/src/main/res/drawable-nodpi/timber_5_nowplaying_x.png b/app/src/main/res/drawable-nodpi/timber_5_nowplaying_x.png new file mode 100755 index 000000000..6a4a03559 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/timber_5_nowplaying_x.png differ diff --git a/app/src/main/res/drawable-nodpi/timber_6_nowplaying_x.png b/app/src/main/res/drawable-nodpi/timber_6_nowplaying_x.png new file mode 100755 index 000000000..b593773f9 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/timber_6_nowplaying_x.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_small.png b/app/src/main/res/drawable-nodpi/widget_small.png new file mode 100644 index 000000000..aeb2920e2 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_small.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_standard.png b/app/src/main/res/drawable-nodpi/widget_standard.png new file mode 100644 index 000000000..fe29eba4e Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_standard.png differ diff --git a/app/src/main/res/drawable-nodpi/widget_white.png b/app/src/main/res/drawable-nodpi/widget_white.png new file mode 100644 index 000000000..f2bd0fb11 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_lock_outline_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_lock_outline_white_24dp.png new file mode 100644 index 000000000..f60060132 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_lock_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu.png b/app/src/main/res/drawable-xhdpi/ic_menu.png deleted file mode 100644 index 8962cbd91..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_menu.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 000000000..9f10aa275 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 000000000..197203bf0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_more_vert_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/payment_black.png b/app/src/main/res/drawable-xhdpi/payment_black.png new file mode 100644 index 000000000..880129324 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/payment_black.png differ diff --git a/app/src/main/res/drawable-xhdpi/payment_white.png b/app/src/main/res/drawable-xhdpi/payment_white.png new file mode 100644 index 000000000..63428ffa2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/payment_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_lock_outline_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_lock_outline_white_24dp.png new file mode 100644 index 000000000..409703a60 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_lock_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 000000000..94d5ab98c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 000000000..8492369d0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_more_vert_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/payment_black.png b/app/src/main/res/drawable-xxhdpi/payment_black.png new file mode 100644 index 000000000..1c02a9f43 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/payment_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/payment_white.png b/app/src/main/res/drawable-xxhdpi/payment_white.png new file mode 100644 index 000000000..74f6937b8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/payment_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_lock_outline_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_lock_outline_white_24dp.png new file mode 100644 index 000000000..53dbe2361 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_lock_outline_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 000000000..4642a3b66 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png new file mode 100644 index 000000000..27779e7a7 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_more_vert_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/payment_black.png b/app/src/main/res/drawable-xxxhdpi/payment_black.png new file mode 100644 index 000000000..bd800ec82 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/payment_black.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/payment_white.png b/app/src/main/res/drawable-xxxhdpi/payment_white.png new file mode 100644 index 000000000..d393d7c06 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/payment_white.png differ diff --git a/app/src/main/res/drawable/ic_file_music_dark.xml b/app/src/main/res/drawable/ic_file_music_dark.xml new file mode 100644 index 000000000..47cc39b56 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_music_dark.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_folder_open_black_24dp.xml b/app/src/main/res/drawable/ic_folder_open_black_24dp.xml new file mode 100644 index 000000000..b71523ac0 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder_open_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_folder_open_white_24dp.xml b/app/src/main/res/drawable/ic_folder_open_white_24dp.xml new file mode 100644 index 000000000..09db55b31 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder_open_white_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_folder_parent_dark.xml b/app/src/main/res/drawable/ic_folder_parent_dark.xml new file mode 100644 index 000000000..060ddb429 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder_parent_dark.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 000000000..a2ea521f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_timer_wait.xml b/app/src/main/res/drawable/ic_timer_wait.xml new file mode 100644 index 000000000..135c99e7e --- /dev/null +++ b/app/src/main/res/drawable/ic_timer_wait.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_donate.xml b/app/src/main/res/layout/activity_donate.xml new file mode 100644 index 000000000..dc40f91da --- /dev/null +++ b/app/src/main/res/layout/activity_donate.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3d965d645..97336581e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_playlist_detail.xml b/app/src/main/res/layout/activity_playlist_detail.xml index 1ef82de85..8be76c470 100644 --- a/app/src/main/res/layout/activity_playlist_detail.xml +++ b/app/src/main/res/layout/activity_playlist_detail.xml @@ -6,52 +6,61 @@ android:layout_height="match_parent"> + android:transitionName="transition_album_art" /> + android:transitionName="transition_foreground" /> - - - - - + android:orientation="vertical"> - + + + + + + android:layout_marginLeft="5dp" + android:layout_marginTop="30dp" + android:background="@android:color/transparent"> + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index 3fa520b31..4a504fbab 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -6,13 +6,13 @@ android:fitsSystemWindows="true" android:orientation="vertical"> - - - - diff --git a/app/src/main/res/layout/dialog_lastfm_login.xml b/app/src/main/res/layout/dialog_lastfm_login.xml new file mode 100644 index 000000000..24c2ce74f --- /dev/null +++ b/app/src/main/res/layout/dialog_lastfm_login.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/empty_view.xml b/app/src/main/res/layout/empty_view.xml new file mode 100644 index 000000000..64b95f6ab --- /dev/null +++ b/app/src/main/res/layout/empty_view.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_album_detail.xml b/app/src/main/res/layout/fragment_album_detail.xml index 0273825d0..51f67a578 100644 --- a/app/src/main/res/layout/fragment_album_detail.xml +++ b/app/src/main/res/layout/fragment_album_detail.xml @@ -1,18 +1,18 @@ - - - @@ -86,7 +87,7 @@ - - - + + - - - + diff --git a/app/src/main/res/layout/fragment_artist_bio.xml b/app/src/main/res/layout/fragment_artist_bio.xml index 61e047959..7aed7a9bf 100644 --- a/app/src/main/res/layout/fragment_artist_bio.xml +++ b/app/src/main/res/layout/fragment_artist_bio.xml @@ -1,5 +1,5 @@ - - @@ -22,7 +22,7 @@ android:id="@+id/artist_bio" android:textSize="40sp"/> - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_artist_detail.xml b/app/src/main/res/layout/fragment_artist_detail.xml index 42596772f..bf7d53270 100644 --- a/app/src/main/res/layout/fragment_artist_detail.xml +++ b/app/src/main/res/layout/fragment_artist_detail.xml @@ -1,18 +1,18 @@ - - - - - + - + - + - + diff --git a/app/src/main/res/layout/fragment_artist_music.xml b/app/src/main/res/layout/fragment_artist_music.xml index b8a4d0d04..2fe8fc52a 100644 --- a/app/src/main/res/layout/fragment_artist_music.xml +++ b/app/src/main/res/layout/fragment_artist_music.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_folders.xml b/app/src/main/res/layout/fragment_folders.xml new file mode 100644 index 000000000..64818271a --- /dev/null +++ b/app/src/main/res/layout/fragment_folders.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_lyrics.xml b/app/src/main/res/layout/fragment_lyrics.xml new file mode 100644 index 000000000..4c35f2988 --- /dev/null +++ b/app/src/main/res/layout/fragment_lyrics.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index 54e818f3c..376f5bd5e 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -1,34 +1,34 @@ - - - - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playback_controls.xml b/app/src/main/res/layout/fragment_playback_controls.xml index c5c25a967..c732c2e62 100644 --- a/app/src/main/res/layout/fragment_playback_controls.xml +++ b/app/src/main/res/layout/fragment_playback_controls.xml @@ -3,7 +3,8 @@ + android:layout_height="match_parent" + android:id="@+id/root_view"> - - + android:layout_height="match_parent"> + + + + + + diff --git a/app/src/main/res/layout/fragment_playlist_pager.xml b/app/src/main/res/layout/fragment_playlist_pager.xml index fa7e4398c..01f3b6754 100644 --- a/app/src/main/res/layout/fragment_playlist_pager.xml +++ b/app/src/main/res/layout/fragment_playlist_pager.xml @@ -59,6 +59,16 @@ android:layout_height="wrap_content" android:id="@+id/songcount" /> + @@ -85,6 +95,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Auto\nPlaylist" + android:visibility="gone" android:id="@+id/playlisttype"/> diff --git a/app/src/main/res/layout/fragment_queue.xml b/app/src/main/res/layout/fragment_queue.xml index 8b3b019c2..aef2c65b1 100644 --- a/app/src/main/res/layout/fragment_queue.xml +++ b/app/src/main/res/layout/fragment_queue.xml @@ -1,24 +1,28 @@ - - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_recyclerview.xml b/app/src/main/res/layout/fragment_recyclerview.xml index d82da0624..ca19ae5d6 100644 --- a/app/src/main/res/layout/fragment_recyclerview.xml +++ b/app/src/main/res/layout/fragment_recyclerview.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:layout_marginBottom="55dp"> - + + + diff --git a/app/src/main/res/layout/fragment_timber1.xml b/app/src/main/res/layout/fragment_timber1.xml index 36a5073de..5631c28f0 100644 --- a/app/src/main/res/layout/fragment_timber1.xml +++ b/app/src/main/res/layout/fragment_timber1.xml @@ -1,11 +1,11 @@ - - - - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timber2.xml b/app/src/main/res/layout/fragment_timber2.xml index b07a9dd93..c11ca606b 100644 --- a/app/src/main/res/layout/fragment_timber2.xml +++ b/app/src/main/res/layout/fragment_timber2.xml @@ -1,11 +1,11 @@ - - + android:textSize="21sp" + android:visibility="gone"/> + android:textSize="13sp" + android:visibility="gone"/> - - + - - - + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timber3.xml b/app/src/main/res/layout/fragment_timber3.xml index b90c6b1bf..f58b61334 100644 --- a/app/src/main/res/layout/fragment_timber3.xml +++ b/app/src/main/res/layout/fragment_timber3.xml @@ -1,11 +1,11 @@ - - - + android:textSize="16sp" /> - - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timber4.xml b/app/src/main/res/layout/fragment_timber4.xml index 0b7c27936..b1b75a533 100644 --- a/app/src/main/res/layout/fragment_timber4.xml +++ b/app/src/main/res/layout/fragment_timber4.xml @@ -1,5 +1,5 @@ - @@ -15,7 +15,7 @@ android:layout_height="match_parent" android:background="#77000000" /> - + android:textSize="23sp" + android:visibility="gone"/> + android:textSize="13sp" + android:visibility="gone"/> - - + - - - - + - + diff --git a/app/src/main/res/layout/fragment_timber5.xml b/app/src/main/res/layout/fragment_timber5.xml new file mode 100644 index 000000000..adfb0804a --- /dev/null +++ b/app/src/main/res/layout/fragment_timber5.xml @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_timber6.xml b/app/src/main/res/layout/fragment_timber6.xml new file mode 100644 index 000000000..ab83c80dc --- /dev/null +++ b/app/src/main/res/layout/fragment_timber6.xml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/include_list_viewpager.xml b/app/src/main/res/layout/include_list_viewpager.xml index 0787cbc8a..a60074536 100644 --- a/app/src/main/res/layout/include_list_viewpager.xml +++ b/app/src/main/res/layout/include_list_viewpager.xml @@ -1,29 +1,35 @@ - - + android:layout_width="match_parent" + android:id="@+id/content_root" + xmlns:app="http://schemas.android.com/apk/res-auto"> - + android:layout_height="match_parent" + android:gravity="bottom" + sothree:fadeColor="@android:color/transparent" + sothree:panelHeight="55dp" + sothree:shadowHeight="5dp"> - + + + + + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_album_grid.xml b/app/src/main/res/layout/item_album_grid.xml index 08bb91c15..cb89bc673 100644 --- a/app/src/main/res/layout/item_album_grid.xml +++ b/app/src/main/res/layout/item_album_grid.xml @@ -22,7 +22,6 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="centerCrop" - android:src="@drawable/ic_launcher" android:transitionName="transition_album_art" /> - + android:src="@drawable/ic_more_vert_black_24dp" /> diff --git a/app/src/main/res/layout/item_artist_album.xml b/app/src/main/res/layout/item_artist_album.xml index 9728b0ad8..ec0529fb1 100644 --- a/app/src/main/res/layout/item_artist_album.xml +++ b/app/src/main/res/layout/item_artist_album.xml @@ -1,4 +1,4 @@ - - + android:src="@drawable/ic_empty_music2" + android:transitionName="transition_album_art" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_artist_song.xml b/app/src/main/res/layout/item_artist_song.xml index f8c8277e8..92d75979f 100644 --- a/app/src/main/res/layout/item_artist_song.xml +++ b/app/src/main/res/layout/item_artist_song.xml @@ -67,7 +67,7 @@ android:visibility="invisible" android:layout_toLeftOf="@+id/popup_menu"/> - + android:src="@drawable/ic_more_vert_black_24dp" /> diff --git a/app/src/main/res/layout/item_donate_product.xml b/app/src/main/res/layout/item_donate_product.xml new file mode 100644 index 000000000..8ccf3f31c --- /dev/null +++ b/app/src/main/res/layout/item_donate_product.xml @@ -0,0 +1,25 @@ + + + + + +