commit 32f76f541850677ea6b97d733a92f4207cee04cf Author: Karim Abou Zeid Date: Sun Jan 25 01:05:25 2015 +0100 - git (re)init (git structure was corrupted) - added shuffler and repeat mode - xxxhdpi icons - typos - new styles - not fully working playing queue [alpha] diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bfb19ef6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +app/src/main/res/values/com_crashlytics_export_strings.xml +app/src/main/assets/crashlytics-build.properties +app/app-release.apk +app/build +app/crashlytics.properties + +# built application files +*.apk +*.ap_ + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ +target/ + +# external libraries +libs/ + +# gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties +.gitattributes + +# Eclipse project files +.classpath +.project + +# Android Studio +*.iml +.idea + +# Mac +.DS_Store +gradle \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..32815916 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,64 @@ +buildscript { + repositories { + maven { url 'https://maven.fabric.io/public' } + } + + dependencies { + classpath 'io.fabric.tools:gradle:1.+' + } +} +apply plugin: 'com.android.application' +apply plugin: 'io.fabric' + +repositories { + maven { url 'https://maven.fabric.io/public' } +} + + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.1" + + defaultConfig { + applicationId "com.kabouzeid.materialmusic" + minSdkVersion 16 + targetSdkVersion 21 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + buildTypes { + release { + minifyEnabled true + shrinkResources true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:21.0.+' + compile 'com.android.support:gridlayout-v7:21.0.+' + compile 'com.android.support:recyclerview-v7:21.0.+' + compile 'com.android.support:palette-v7:21.0.+' + compile 'com.android.support:support-v13:21.0.+' + compile 'com.nhaarman.listviewanimations:lib-core:3.1.0@aar' + compile 'com.nhaarman.listviewanimations:lib-manipulation:3.1.0@aar' + compile 'com.nhaarman.listviewanimations:lib-core-slh:3.1.0@aar' + compile 'com.nineoldandroids:library:2.4.0+' + compile 'com.melnykov:floatingactionbutton:1.1.+' + compile 'com.github.ksoichiro:android-observablescrollview:1.3.0' + compile 'com.mcxiaoke.volley:library:1.0.+' + compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' + compile 'com.afollestad:material-dialogs:0.6.1.5' + compile('com.crashlytics.sdk.android:crashlytics:2.1.0@aar') { + transitive = true; + } + compile files('/Users/karim/Google Drive/Dokumente/AndroidStudioProjects/MaterialMusic/libs/jaudiotagger-2.0.4-20111207.115108-15.jar') + compile project(':libraries:drag-sort-listview:library') +} diff --git a/app/manifest-merger-release-report.txt b/app/manifest-merger-release-report.txt new file mode 100644 index 00000000..0ab9f9e3 --- /dev/null +++ b/app/manifest-merger-release-report.txt @@ -0,0 +1,192 @@ +-- Merging decision tree log --- +manifest +ADDED from AndroidManifest.xml:2:1 + package + ADDED from AndroidManifest.xml:3:11 + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 + android:versionName + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 + xmlns:android + ADDED from AndroidManifest.xml:2:11 + android:versionCode + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 +uses-permission#android.permission.WAKE_LOCK +ADDED from AndroidManifest.xml:5:5 + android:name + ADDED from AndroidManifest.xml:5:22 +uses-permission#android.permission.READ_EXTERNAL_STORAGE +ADDED from AndroidManifest.xml:6:5 + android:name + ADDED from AndroidManifest.xml:6:22 +uses-permission#android.permission.WRITE_EXTERNAL_STORAGE +ADDED from AndroidManifest.xml:7:5 + android:name + ADDED from AndroidManifest.xml:7:22 +uses-permission#android.permission.VIBRATE +ADDED from AndroidManifest.xml:8:5 + android:name + ADDED from AndroidManifest.xml:8:22 +uses-permission#android.permission.INTERNET +ADDED from AndroidManifest.xml:9:5 +MERGED from com.crashlytics.sdk.android:crashlytics:2.1.0:11:5 + android:name + ADDED from AndroidManifest.xml:9:22 +application +ADDED from AndroidManifest.xml:11:5 +MERGED from com.android.support:appcompat-v7:21.0.3:16:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:gridlayout-v7:21.0.3:16:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:recyclerview-v7:21.0.3:17:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:palette-v7:21.0.3:16:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:support-v13:21.0.3:16:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.nhaarman.listviewanimations:lib-core:3.1.0:26:5 +MERGED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:26:5 +MERGED from com.nhaarman.listviewanimations:lib-core-slh:3.1.0:26:5 +MERGED from com.melnykov:floatingactionbutton:1.1.0:12:5 +MERGED from com.android.support:recyclerview-v7:21.0.3:17:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:recyclerview-v7:21.0.3:17:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:appcompat-v7:21.0.3:16:5 +MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.crashlytics.sdk.android:crashlytics:2.1.0:13:5 +MERGED from com.crashlytics.sdk.android:beta:1.0.2:11:5 +MERGED from io.fabric.sdk.android:fabric:1.0.2:11:5 +MERGED from com.crashlytics.sdk.android:answers:1.0.2:11:5 +MERGED from io.fabric.sdk.android:fabric:1.0.2:11:5 +MERGED from io.fabric.sdk.android:fabric:1.0.2:11:5 + android:label + ADDED from AndroidManifest.xml:15:9 + android:allowBackup + ADDED from AndroidManifest.xml:13:9 + android:icon + ADDED from AndroidManifest.xml:14:9 + android:theme + ADDED from AndroidManifest.xml:16:9 + android:name + ADDED from AndroidManifest.xml:12:9 +activity#com.kabouzeid.materialmusic.ui.activities.MainActivity +ADDED from AndroidManifest.xml:17:9 + android:label + ADDED from AndroidManifest.xml:19:13 + android:name + ADDED from AndroidManifest.xml:18:13 +intent-filter#android.intent.action.MAIN+android.intent.category.LAUNCHER +ADDED from AndroidManifest.xml:20:13 +action#android.intent.action.MAIN +ADDED from AndroidManifest.xml:21:17 + android:name + ADDED from AndroidManifest.xml:21:25 +category#android.intent.category.LAUNCHER +ADDED from AndroidManifest.xml:23:17 + android:name + ADDED from AndroidManifest.xml:23:27 +activity#com.kabouzeid.materialmusic.ui.activities.AlbumDetailActivity +ADDED from AndroidManifest.xml:26:9 + android:name + ADDED from AndroidManifest.xml:26:19 +activity#com.kabouzeid.materialmusic.ui.activities.ArtistDetailActivity +ADDED from AndroidManifest.xml:28:9 + android:name + ADDED from AndroidManifest.xml:28:19 +activity#com.kabouzeid.materialmusic.ui.activities.MusicControllerActivity +ADDED from AndroidManifest.xml:30:9 + android:parentActivityName + ADDED from AndroidManifest.xml:32:13 + android:name + ADDED from AndroidManifest.xml:31:13 +service#com.kabouzeid.materialmusic.service.MusicService +ADDED from AndroidManifest.xml:35:9 + android:enabled + ADDED from AndroidManifest.xml:37:13 + android:name + ADDED from AndroidManifest.xml:36:13 +receiver#com.kabouzeid.materialmusic.service.MediaButtonIntentReceiver +ADDED from AndroidManifest.xml:40:9 + android:name + ADDED from AndroidManifest.xml:40:19 +intent-filter#android.intent.action.MEDIA_BUTTON +ADDED from AndroidManifest.xml:41:13 +action#android.intent.action.MEDIA_BUTTON +ADDED from AndroidManifest.xml:42:17 + android:name + ADDED from AndroidManifest.xml:42:25 +meta-data#com.crashlytics.ApiKey +ADDED from AndroidManifest.xml:46:9 + android:value + ADDED from AndroidManifest.xml:48:13 + android:name + ADDED from AndroidManifest.xml:47:13 +activity#com.kabouzeid.materialmusic.ui.activities.tageditor.SongTagEditorActivity +ADDED from AndroidManifest.xml:50:9 + android:label + ADDED from AndroidManifest.xml:52:13 + android:windowSoftInputMode + ADDED from AndroidManifest.xml:53:13 + android:name + ADDED from AndroidManifest.xml:51:13 +activity#com.kabouzeid.materialmusic.ui.activities.tageditor.AlbumTagEditorActivity +ADDED from AndroidManifest.xml:55:9 + android:label + ADDED from AndroidManifest.xml:57:13 + android:name + ADDED from AndroidManifest.xml:56:13 +uses-sdk +INJECTED from AndroidManifest.xml:0:0 reason: use-sdk injection requested +MERGED from com.android.support:appcompat-v7:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.android.support:gridlayout-v7:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.android.support:recyclerview-v7:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.android.support:palette-v7:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.android.support:support-v13:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.nhaarman.listviewanimations:lib-core:3.1.0:22:5 +MERGED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:22:5 +MERGED from com.nhaarman.listviewanimations:lib-core-slh:3.1.0:22:5 +MERGED from com.melnykov:floatingactionbutton:1.1.0:8:5 +MERGED from com.android.support:recyclerview-v7:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.github.ksoichiro:android-observablescrollview:1.3.0:21:5 +MERGED from com.android.support:recyclerview-v7:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.afollestad:material-dialogs:0.4.8:8:5 +MERGED from com.android.support:appcompat-v7:21.0.3:15:5 +MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.crashlytics.sdk.android:crashlytics:2.1.0:7:5 +MERGED from com.crashlytics.sdk.android:beta:1.0.2:7:5 +MERGED from io.fabric.sdk.android:fabric:1.0.2:7:5 +MERGED from com.crashlytics.sdk.android:answers:1.0.2:7:5 +MERGED from io.fabric.sdk.android:fabric:1.0.2:7:5 +MERGED from io.fabric.sdk.android:fabric:1.0.2:7:5 + android:targetSdkVersion + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 + android:minSdkVersion + INJECTED from AndroidManifest.xml:0:0 + INJECTED from AndroidManifest.xml:0:0 +activity#android.support.v7.widget.TestActivity +ADDED from com.android.support:recyclerview-v7:21.0.3:18:9 +MERGED from com.android.support:recyclerview-v7:21.0.3:18:9 +MERGED from com.android.support:recyclerview-v7:21.0.3:18:9 + android:label + ADDED from com.android.support:recyclerview-v7:21.0.3:18:19 + android:name + ADDED from com.android.support:recyclerview-v7:21.0.3:18:60 +activity#com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeTouchListenerTestActivity +ADDED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:27:9 + android:name + ADDED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:27:19 +activity#com.nhaarman.listviewanimations.itemmanipulation.dragdrop.DynamicListViewTestActivity +ADDED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:28:9 + android:name + ADDED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:28:19 diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..56446cc5 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Applications/android_sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} +-keep class !android.support.v7.internal.view.menu.**,** {*;} +-dontwarn +-ignorewarnings +-dontshrink \ No newline at end of file diff --git a/app/src/androidTest/java/com/kabouzeid/materialmusic/ApplicationTest.java b/app/src/androidTest/java/com/kabouzeid/materialmusic/ApplicationTest.java new file mode 100644 index 00000000..59b8d703 --- /dev/null +++ b/app/src/androidTest/java/com/kabouzeid/materialmusic/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.kabouzeid.materialmusic; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..27e46cd5 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/google/samples/apps/iosched/ui/widget/SlidingTabLayout.java b/app/src/main/java/com/google/samples/apps/iosched/ui/widget/SlidingTabLayout.java new file mode 100644 index 00000000..92672500 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/iosched/ui/widget/SlidingTabLayout.java @@ -0,0 +1,321 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.iosched.ui.widget; + +import android.content.Context; +import android.graphics.Typeface; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * To be used with ViewPager to provide a tab indicator component which give constant feedback as to + * the user's scroll progress. + *

+ * To use the component, simply add it to your view hierarchy. Then in your + * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call + * {@link #setViewPager(android.support.v4.view.ViewPager)} providing it the ViewPager this layout is being used for. + *

+ * The colors can be customized in two ways. The first and simplest is to provide an array of colors + * via {@link #setSelectedIndicatorColors(int...)}. The + * alternative is via the {@link com.google.samples.apps.iosched.ui.widget.SlidingTabLayout.TabColorizer} interface which provides you complete control over + * which color is used for any individual position. + *

+ * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)}, + * providing the layout ID of your custom layout. + */ +public class SlidingTabLayout extends HorizontalScrollView { + /** + * Allows complete control over the colors drawn in the tab layout. Set with + * {@link #setCustomTabColorizer(com.google.samples.apps.iosched.ui.widget.SlidingTabLayout.TabColorizer)}. + */ + public interface TabColorizer { + + /** + * @return return the color of the indicator used when {@code position} is selected. + */ + int getIndicatorColor(int position); + + } + + private static final int TITLE_OFFSET_DIPS = 24; + private static final int TAB_VIEW_PADDING_DIPS = 16; + private static final int TAB_VIEW_TEXT_SIZE_SP = 12; + + private int mTitleOffset; + + private int mTabViewLayoutId; + private int mTabViewTextViewId; + private boolean mDistributeEvenly; + + private ViewPager mViewPager; + private SparseArray mContentDescriptions = new SparseArray(); + private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; + + private final SlidingTabStrip mTabStrip; + + public SlidingTabLayout(Context context) { + this(context, null); + } + + public SlidingTabLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + // Disable the Scroll Bar + setHorizontalScrollBarEnabled(false); + // Make sure that the Tab Strips fills this View + setFillViewport(true); + + mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); + + mTabStrip = new SlidingTabStrip(context); + addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + + /** + * Set the custom {@link com.google.samples.apps.iosched.ui.widget.SlidingTabLayout.TabColorizer} to be used. + *

+ * If you only require simple custmisation then you can use + * {@link #setSelectedIndicatorColors(int...)} to achieve + * similar effects. + */ + public void setCustomTabColorizer(TabColorizer tabColorizer) { + mTabStrip.setCustomTabColorizer(tabColorizer); + } + + public void setDistributeEvenly(boolean distributeEvenly) { + mDistributeEvenly = distributeEvenly; + } + + /** + * Sets the colors to be used for indicating the selected tab. These colors are treated as a + * circular array. Providing one color will mean that all tabs are indicated with the same color. + */ + public void setSelectedIndicatorColors(int... colors) { + mTabStrip.setSelectedIndicatorColors(colors); + } + + /** + * Set the {@link android.support.v4.view.ViewPager.OnPageChangeListener}. When using {@link com.google.samples.apps.iosched.ui.widget.SlidingTabLayout} you are + * required to set any {@link android.support.v4.view.ViewPager.OnPageChangeListener} through this method. This is so + * that the layout can update it's scroll position correctly. + * + * @see android.support.v4.view.ViewPager#setOnPageChangeListener(android.support.v4.view.ViewPager.OnPageChangeListener) + */ + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mViewPagerPageChangeListener = listener; + } + + /** + * Set the custom layout to be inflated for the tab views. + * + * @param layoutResId Layout id to be inflated + * @param textViewId id of the {@link android.widget.TextView} in the inflated view + */ + public void setCustomTabView(int layoutResId, int textViewId) { + mTabViewLayoutId = layoutResId; + mTabViewTextViewId = textViewId; + } + + /** + * Sets the associated view pager. Note that the assumption here is that the pager content + * (number of tabs and tab titles) does not change after this call has been made. + */ + public void setViewPager(ViewPager viewPager) { + mTabStrip.removeAllViews(); + + mViewPager = viewPager; + if (viewPager != null) { + viewPager.setOnPageChangeListener(new InternalViewPagerListener()); + populateTabStrip(); + } + } + + /** + * Create a default view to be used for tabs. This is called if a custom tab view is not set via + * {@link #setCustomTabView(int, int)}. + */ + protected TextView createDefaultTabView(Context context) { + TextView textView = new TextView(context); + textView.setGravity(Gravity.CENTER); + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); + textView.setTypeface(Typeface.DEFAULT_BOLD); + textView.setLayoutParams(new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + TypedValue outValue = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, + outValue, true); + textView.setBackgroundResource(outValue.resourceId); + textView.setAllCaps(true); + + int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density); + textView.setPadding(padding, padding, padding, padding); + + return textView; + } + + private void populateTabStrip() { + final PagerAdapter adapter = mViewPager.getAdapter(); + final OnClickListener tabClickListener = new TabClickListener(); + + for (int i = 0; i < adapter.getCount(); i++) { + View tabView = null; + TextView tabTitleView = null; + + if (mTabViewLayoutId != 0) { + // If there is a custom tab view layout id set, try and inflate it + tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip, + false); + tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId); + } + + if (tabView == null) { + tabView = createDefaultTabView(getContext()); + } + + if (tabTitleView == null && TextView.class.isInstance(tabView)) { + tabTitleView = (TextView) tabView; + } + + if (mDistributeEvenly) { + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) tabView.getLayoutParams(); + lp.width = 0; + lp.weight = 1; + } + + tabTitleView.setText(adapter.getPageTitle(i)); + tabView.setOnClickListener(tabClickListener); + String desc = mContentDescriptions.get(i, null); + if (desc != null) { + tabView.setContentDescription(desc); + } + + mTabStrip.addView(tabView); + if (i == mViewPager.getCurrentItem()) { + tabView.setSelected(true); + } + } + } + + public void setContentDescription(int i, String desc) { + mContentDescriptions.put(i, desc); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mViewPager != null) { + scrollToTab(mViewPager.getCurrentItem(), 0); + } + } + + private void scrollToTab(int tabIndex, int positionOffset) { + final int tabStripChildCount = mTabStrip.getChildCount(); + if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { + return; + } + + View selectedChild = mTabStrip.getChildAt(tabIndex); + if (selectedChild != null) { + int targetScrollX = selectedChild.getLeft() + positionOffset; + + if (tabIndex > 0 || positionOffset > 0) { + // If we're not at the first child and are mid-scroll, make sure we obey the offset + targetScrollX -= mTitleOffset; + } + + scrollTo(targetScrollX, 0); + } + } + + private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { + private int mScrollState; + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + int tabStripChildCount = mTabStrip.getChildCount(); + if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { + return; + } + + mTabStrip.onViewPagerPageChanged(position, positionOffset); + + View selectedTitle = mTabStrip.getChildAt(position); + int extraOffset = (selectedTitle != null) + ? (int) (positionOffset * selectedTitle.getWidth()) + : 0; + scrollToTab(position, extraOffset); + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, + positionOffsetPixels); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + mTabStrip.onViewPagerPageChanged(position, 0f); + scrollToTab(position, 0); + } + for (int i = 0; i < mTabStrip.getChildCount(); i++) { + mTabStrip.getChildAt(i).setSelected(position == i); + } + if (mViewPagerPageChangeListener != null) { + mViewPagerPageChangeListener.onPageSelected(position); + } + } + + } + + private class TabClickListener implements OnClickListener { + @Override + public void onClick(View v) { + for (int i = 0; i < mTabStrip.getChildCount(); i++) { + if (v == mTabStrip.getChildAt(i)) { + mViewPager.setCurrentItem(i); + return; + } + } + } + } + +} diff --git a/app/src/main/java/com/google/samples/apps/iosched/ui/widget/SlidingTabStrip.java b/app/src/main/java/com/google/samples/apps/iosched/ui/widget/SlidingTabStrip.java new file mode 100644 index 00000000..ae2c1fb9 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/iosched/ui/widget/SlidingTabStrip.java @@ -0,0 +1,168 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.iosched.ui.widget; + +import android.R; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.widget.LinearLayout; + +class SlidingTabStrip extends LinearLayout { + + private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0; + private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; + private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3; + private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5; + + private final int mBottomBorderThickness; + private final Paint mBottomBorderPaint; + + private final int mSelectedIndicatorThickness; + private final Paint mSelectedIndicatorPaint; + + private final int mDefaultBottomBorderColor; + + private int mSelectedPosition; + private float mSelectionOffset; + + private SlidingTabLayout.TabColorizer mCustomTabColorizer; + private final SimpleTabColorizer mDefaultTabColorizer; + + SlidingTabStrip(Context context) { + this(context, null); + } + + SlidingTabStrip(Context context, AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(false); + + final float density = getResources().getDisplayMetrics().density; + + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true); + final int themeForegroundColor = outValue.data; + + mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, + DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); + + mDefaultTabColorizer = new SimpleTabColorizer(); + mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); + + mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); + mBottomBorderPaint = new Paint(); + mBottomBorderPaint.setColor(mDefaultBottomBorderColor); + + mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); + mSelectedIndicatorPaint = new Paint(); + } + + void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) { + mCustomTabColorizer = customTabColorizer; + invalidate(); + } + + void setSelectedIndicatorColors(int... colors) { + // Make sure that the custom colorizer is removed + mCustomTabColorizer = null; + mDefaultTabColorizer.setIndicatorColors(colors); + invalidate(); + } + + void onViewPagerPageChanged(int position, float positionOffset) { + mSelectedPosition = position; + mSelectionOffset = positionOffset; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + final int height = getHeight(); + final int childCount = getChildCount(); + final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null + ? mCustomTabColorizer + : mDefaultTabColorizer; + + // Thick colored underline below the current selection + if (childCount > 0) { + View selectedTitle = getChildAt(mSelectedPosition); + int left = selectedTitle.getLeft(); + int right = selectedTitle.getRight(); + int color = tabColorizer.getIndicatorColor(mSelectedPosition); + + if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { + int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); + if (color != nextColor) { + color = blendColors(nextColor, color, mSelectionOffset); + } + + // Draw the selection partway between the tabs + View nextTitle = getChildAt(mSelectedPosition + 1); + left = (int) (mSelectionOffset * nextTitle.getLeft() + + (1.0f - mSelectionOffset) * left); + right = (int) (mSelectionOffset * nextTitle.getRight() + + (1.0f - mSelectionOffset) * right); + } + + mSelectedIndicatorPaint.setColor(color); + + canvas.drawRect(left, height - mSelectedIndicatorThickness, right, + height, mSelectedIndicatorPaint); + } + + // Thin underline along the entire bottom edge + canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); + } + + /** + * Set the alpha value of the {@code color} to be the given {@code alpha} value. + */ + private static int setColorAlpha(int color, byte alpha) { + return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + /** + * Blend {@code color1} and {@code color2} using the given ratio. + * + * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, + * 0.0 will return {@code color2}. + */ + private static int blendColors(int color1, int color2, float ratio) { + final float inverseRation = 1f - ratio; + float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); + float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); + float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); + return Color.rgb((int) r, (int) g, (int) b); + } + + private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer { + private int[] mIndicatorColors; + + @Override + public final int getIndicatorColor(int position) { + return mIndicatorColors[position % mIndicatorColors.length]; + } + + void setIndicatorColors(int... colors) { + mIndicatorColors = colors; + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/App.java b/app/src/main/java/com/kabouzeid/materialmusic/App.java new file mode 100644 index 00000000..20f8cc4c --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/App.java @@ -0,0 +1,84 @@ +package com.kabouzeid.materialmusic; + +import android.app.Application; +import android.app.Fragment; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.preference.PreferenceManager; + +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.Volley; +import com.crashlytics.android.Crashlytics; +import com.kabouzeid.materialmusic.helper.MusicPlayerRemote; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.util.ImageLoaderUtil; + +import io.fabric.sdk.android.Fabric; + +/** + * Created by karim on 25.11.14. + */ +public class App extends Application { + private static final String TAG = App.class.getSimpleName(); + + public Fragment[] MainActivityFragments = new Fragment[5]; + private MusicPlayerRemote playerRemote; + private int appTheme; + private SharedPreferences defaultSharedPreferences; + private RequestQueue requestQueue; + + @Override + public void onCreate() { + super.onCreate(); + Fabric.with(this, new Crashlytics()); + ImageLoaderUtil.initImageLoader(this); + } + + public MusicPlayerRemote getMusicPlayerRemote() { + if (playerRemote == null) { + playerRemote = new MusicPlayerRemote(this); + playerRemote.restorePreviousState(); + } + return playerRemote; + } + + public SharedPreferences getDefaultSharedPreferences() { + if (defaultSharedPreferences == null) { + defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + } + return defaultSharedPreferences; + } + + public int getAppTheme() { + if (appTheme == 0) { + appTheme = getDefaultSharedPreferences().getInt(AppKeys.SP_THEME, R.style.Theme_MaterialMusic); + } + return appTheme; + } + + public void setAppTheme(int appTheme) { + this.appTheme = appTheme; + defaultSharedPreferences.edit().putInt(AppKeys.SP_THEME, appTheme); + } + + public boolean isTablet() { + return getResources().getConfiguration().smallestScreenWidthDp >= 600; + } + + public boolean isInPortraitMode() { + return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + } + + public RequestQueue getRequestQueue() { + if (requestQueue == null) { + requestQueue = Volley.newRequestQueue(this); + } + return requestQueue; + } + + public void addToRequestQueue(Request request) { + request.setTag(TAG); + getRequestQueue().add(request); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/adapter/AlbumViewGridAdapter.java b/app/src/main/java/com/kabouzeid/materialmusic/adapter/AlbumViewGridAdapter.java new file mode 100644 index 00000000..c5828f90 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/adapter/AlbumViewGridAdapter.java @@ -0,0 +1,105 @@ +package com.kabouzeid.materialmusic.adapter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.v7.graphics.Palette; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.model.Album; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.kabouzeid.materialmusic.util.Util; +import com.kabouzeid.materialmusic.util.ViewUtil; +import com.kabouzeid.materialmusic.view.SquareImageView; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; + +import java.util.List; + +/** + * Created by karim on 24.11.14. + */ +public class AlbumViewGridAdapter extends ArrayAdapter { + public static final String TAG = AlbumViewGridAdapter.class.getSimpleName(); + private Context context; + private boolean usePalette; + + public AlbumViewGridAdapter(Context context, List objects) { + super(context, R.layout.album_tile, objects); + this.context = context; + usePalette = true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Album album = getItem(position); + if (convertView == null) { + convertView = LayoutInflater.from(context).inflate(R.layout.album_tile, parent, false); + } + final SquareImageView albumArt = (SquareImageView) convertView.findViewById(R.id.album_art); + final TextView title = (TextView) convertView.findViewById(R.id.album_title); + final TextView artist = (TextView) convertView.findViewById(R.id.album_interpret); + final View footer = convertView.findViewById(R.id.footer); + + title.setText(album.title); + artist.setText(album.artistName); + + ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(album.id).toString(), albumArt, new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) { + albumArt.setImageDrawable(null); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + if (usePalette) { + paletteBugFixBlackAndWhite(title, artist, footer); + } + albumArt.setImageResource(R.drawable.default_album_art); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + if (usePalette) { + applyPalette(loadedImage, title, artist, footer); + } + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + + } + }); + + return convertView; + } + + private void applyPalette(Bitmap bitmap, final TextView title, final TextView artist, final View footer) { + Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + final Palette.Swatch vibrantSwatch = palette.getVibrantSwatch(); + if (vibrantSwatch != null) { + title.setTextColor(vibrantSwatch.getTitleTextColor()); + artist.setTextColor(vibrantSwatch.getTitleTextColor()); + ViewUtil.animateViewColor(footer, Util.resolveColor(context, R.attr.colorPrimary), + vibrantSwatch.getRgb()); + } else { + paletteBugFixBlackAndWhite(title, artist, footer); + } + } + }); + } + + private void paletteBugFixBlackAndWhite(TextView title, TextView artist, View footer) { + title.setTextColor(Util.resolveColor(context, R.attr.title_text_color)); + artist.setTextColor(Util.resolveColor(context, R.attr.caption_text_color)); + ViewUtil.animateViewColor(footer, Util.resolveColor(context, R.attr.colorPrimary), + Util.resolveColor(context, R.attr.colorPrimary)); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/adapter/ArtistViewListAdapter.java b/app/src/main/java/com/kabouzeid/materialmusic/adapter/ArtistViewListAdapter.java new file mode 100644 index 00000000..37d1488a --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/adapter/ArtistViewListAdapter.java @@ -0,0 +1,60 @@ +package com.kabouzeid.materialmusic.adapter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.lastfm.artist.LastFMArtistThumbnailLoader; +import com.kabouzeid.materialmusic.model.Artist; + +import java.util.List; + +/** + * Created by karim on 29.12.14. + */ +public class ArtistViewListAdapter extends ArrayAdapter { + private Context context; + + + public ArtistViewListAdapter(Context context, List objects) { + super(context, R.layout.item_artist_view, objects); + this.context = context; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Artist artist = getItem(position); + if (convertView == null) { + convertView = LayoutInflater.from(context).inflate(R.layout.item_artist_view, parent, false); + } + final TextView artistName = (TextView) convertView.findViewById(R.id.artist_name); + final ImageView artistArt = (ImageView) convertView.findViewById(R.id.artist_image); + + artistName.setText(artist.name); + artistArt.setImageResource(R.drawable.default_artist_image); + + final Object tag = artist.name; + artistArt.setTag(tag); + + LastFMArtistThumbnailLoader.loadArtistThumbnail(context, artist.name, new LastFMArtistThumbnailLoader.ArtistThumbnailLoaderCallback() { + @Override + public void onArtistThumbnailLoaded(Bitmap thumbnail) { + if (artistArt.getTag().equals(tag)) { + if (thumbnail != null) { + artistArt.setImageBitmap(thumbnail); + } else { + artistArt.setImageResource(R.drawable.default_artist_image); + } + } + } + }); + + return convertView; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/adapter/NavigationDrawerItemAdapter.java b/app/src/main/java/com/kabouzeid/materialmusic/adapter/NavigationDrawerItemAdapter.java new file mode 100644 index 00000000..e2e82a76 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/adapter/NavigationDrawerItemAdapter.java @@ -0,0 +1,51 @@ +package com.kabouzeid.materialmusic.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.model.NavigationDrawerItem; +import com.kabouzeid.materialmusic.util.Util; + +import java.util.List; + +/** + * Created by karim on 23.11.14. + */ +public class NavigationDrawerItemAdapter extends ArrayAdapter { + private int currentChecked = -1; + + public NavigationDrawerItemAdapter(Context context, int resource, List objects) { + super(context, resource, objects); + } + + public void setChecked(int position) { + currentChecked = position; + notifyDataSetChanged(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + NavigationDrawerItem item = getItem(position); + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_navigation_drawer, parent, false); + } + TextView title = (TextView) convertView.findViewById(R.id.title); + ImageView icon = (ImageView) convertView.findViewById(R.id.album_art); + title.setText(item.title); + icon.setImageResource(item.imageRes); + if (position == currentChecked) { + title.setTextColor(Util.resolveColor(getContext(), R.attr.colorAccent)); + } else { + title.setTextColor(Util.resolveColor(getContext(), R.attr.title_text_color)); + } + View container = convertView.findViewById(R.id.container); + container.setActivated(position == currentChecked); + return convertView; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/adapter/PlayListAdapter.java b/app/src/main/java/com/kabouzeid/materialmusic/adapter/PlayListAdapter.java new file mode 100644 index 00000000..9d0f9837 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/adapter/PlayListAdapter.java @@ -0,0 +1,47 @@ +package com.kabouzeid.materialmusic.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.model.Song; + +import java.util.List; + +/** + * Created by karim on 24.01.15. + */ +public class PlayListAdapter extends ArrayAdapter { + private Context context; + private App app; + + public PlayListAdapter(Context context, List playList) { + super(context, R.layout.item_playlist, playList); + this.context = context; + app = (App) context.getApplicationContext(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Song song = getItem(position); + if (convertView == null) { + convertView = LayoutInflater.from(context).inflate(R.layout.item_playlist, parent, false); + } + final TextView title = (TextView) convertView.findViewById(R.id.song_title); + final ImageView albumArt = (ImageView) convertView.findViewById(R.id.album_art); + + title.setText(song.title); + if (app.getMusicPlayerRemote().getCurrentSongId() == song.id) { + albumArt.setImageResource(R.drawable.ic_speaker_white_48dp); + } else { + albumArt.setImageBitmap(null); + } + return convertView; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/adapter/songadapter/SongAdapter.java b/app/src/main/java/com/kabouzeid/materialmusic/adapter/songadapter/SongAdapter.java new file mode 100644 index 00000000..3ce09172 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/adapter/songadapter/SongAdapter.java @@ -0,0 +1,100 @@ +package com.kabouzeid.materialmusic.adapter.songadapter; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.helper.SongDetailDialogHelper; +import com.kabouzeid.materialmusic.loader.SongFileLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.ui.activities.tageditor.SongTagEditorActivity; +import com.kabouzeid.materialmusic.util.MusicUtil; + +import java.io.File; +import java.util.List; + +/** + * Created by karim on 27.11.14. + */ +public class SongAdapter extends ArrayAdapter { + public static final String TAG = SongAdapter.class.getSimpleName(); + protected Context context; + protected GoToAble goToAble; + + public SongAdapter(Context context, GoToAble goToAble, List objects) { + super(context, R.layout.item_song, objects); + this.context = context; + this.goToAble = goToAble; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Song song = getItem(position); + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_song, parent, false); + } + TextView songTitle = (TextView) convertView.findViewById(R.id.song_title); + TextView trackNumber = (TextView) convertView.findViewById(R.id.track_number); + TextView songDuration = (TextView) convertView.findViewById(R.id.song_duration); + ImageView overflowButton = (ImageView) convertView.findViewById(R.id.menu); + + overflowButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + PopupMenu popupMenu = new PopupMenu(context, v); + popupMenu.inflate(R.menu.menu_song); + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_tag_editor: + Intent intent = new Intent(context, SongTagEditorActivity.class); + intent.putExtra(AppKeys.E_ID, song.id); + context.startActivity(intent); + return true; + case R.id.action_details: + String songFilePath = SongFileLoader.getSongFile(context, song.id); + File songFile = new File(songFilePath); + SongDetailDialogHelper.getDialog(context, songFile).show(); + return true; + case R.id.action_go_to_album: + if (goToAble != null) { + goToAble.goToAlbum(song.albumId); + } + return true; + case R.id.action_go_to_artist: + if (goToAble != null) { + goToAble.goToArtist(song.artistId); + } + return true; + } + return false; + } + }); + popupMenu.show(); + } + }); + + songTitle.setText(song.title); + trackNumber.setText(String.valueOf(MusicUtil.getFixedTrackNumber(song.trackNumber))); + songDuration.setText(MusicUtil.getReadableDurationString(song.duration)); + + return convertView; + } + + public static interface GoToAble { + public void goToAlbum(int albumId); + + public void goToArtist(int artistId); + } + +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/adapter/songadapter/SongViewListAdapter.java b/app/src/main/java/com/kabouzeid/materialmusic/adapter/songadapter/SongViewListAdapter.java new file mode 100644 index 00000000..90036742 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/adapter/songadapter/SongViewListAdapter.java @@ -0,0 +1,88 @@ +package com.kabouzeid.materialmusic.adapter.songadapter; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.helper.SongDetailDialogHelper; +import com.kabouzeid.materialmusic.loader.SongFileLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.ui.activities.tageditor.SongTagEditorActivity; +import com.kabouzeid.materialmusic.util.ImageLoaderUtil; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.nostra13.universalimageloader.core.ImageLoader; + +import java.io.File; +import java.util.List; + +/** + * Created by karim on 27.11.14. + */ +public class SongViewListAdapter extends SongAdapter { + public static final String TAG = SongViewListAdapter.class.getSimpleName(); + + public SongViewListAdapter(Context context, SongAdapter.GoToAble goToAble, List objects) { + super(context, goToAble, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Song song = getItem(position); + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_song_view, parent, false); + } + TextView songTitle = (TextView) convertView.findViewById(R.id.song_title); + final ImageView albumArt = (ImageView) convertView.findViewById(R.id.album_art); + ImageView overflowButton = (ImageView) convertView.findViewById(R.id.menu); + + overflowButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View v) { + PopupMenu popupMenu = new PopupMenu(context, v); + popupMenu.inflate(R.menu.menu_song); + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_tag_editor: + Intent intent = new Intent(context, SongTagEditorActivity.class); + intent.putExtra(AppKeys.E_ID, song.id); + context.startActivity(intent); + return true; + case R.id.action_details: + String songFilePath = SongFileLoader.getSongFile(context, song.id); + File songFile = new File(songFilePath); + SongDetailDialogHelper.getDialog(context, songFile).show(); + return true; + case R.id.action_go_to_album: + if (goToAble != null) { + goToAble.goToAlbum(song.albumId); + } + return true; + case R.id.action_go_to_artist: + if (goToAble != null) { + goToAble.goToArtist(song.artistId); + } + return true; + } + return false; + } + }); + popupMenu.show(); + } + }); + + songTitle.setText(song.title); + ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(song.albumId).toString(), albumArt, new ImageLoaderUtil.defaultAlbumArtOnFailed()); + + return convertView; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/comparator/AlbumAlphabeticComparator.java b/app/src/main/java/com/kabouzeid/materialmusic/comparator/AlbumAlphabeticComparator.java new file mode 100644 index 00000000..7a5a91eb --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/comparator/AlbumAlphabeticComparator.java @@ -0,0 +1,15 @@ +package com.kabouzeid.materialmusic.comparator; + +import com.kabouzeid.materialmusic.model.Album; + +import java.util.Comparator; + +/** + * Created by karim on 25.11.14. + */ +public class AlbumAlphabeticComparator implements Comparator { + @Override + public int compare(Album lhs, Album rhs) { + return lhs.title.trim().compareToIgnoreCase(rhs.title.trim()); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/comparator/ArtistAlphabeticComparator.java b/app/src/main/java/com/kabouzeid/materialmusic/comparator/ArtistAlphabeticComparator.java new file mode 100644 index 00000000..8ce27f05 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/comparator/ArtistAlphabeticComparator.java @@ -0,0 +1,15 @@ +package com.kabouzeid.materialmusic.comparator; + +import com.kabouzeid.materialmusic.model.Artist; + +import java.util.Comparator; + +/** + * Created by karim on 29.12.14. + */ +public class ArtistAlphabeticComparator implements Comparator { + @Override + public int compare(Artist lhs, Artist rhs) { + return lhs.name.trim().compareToIgnoreCase(rhs.name.trim()); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/comparator/SongAlphabeticComparator.java b/app/src/main/java/com/kabouzeid/materialmusic/comparator/SongAlphabeticComparator.java new file mode 100644 index 00000000..09040db3 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/comparator/SongAlphabeticComparator.java @@ -0,0 +1,15 @@ +package com.kabouzeid.materialmusic.comparator; + +import com.kabouzeid.materialmusic.model.Song; + +import java.util.Comparator; + +/** + * Created by karim on 28.12.14. + */ +public class SongAlphabeticComparator implements Comparator { + @Override + public int compare(Song lhs, Song rhs) { + return lhs.title.trim().compareToIgnoreCase(rhs.title.trim()); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/comparator/SongTrackNumberComparator.java b/app/src/main/java/com/kabouzeid/materialmusic/comparator/SongTrackNumberComparator.java new file mode 100644 index 00000000..0765d522 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/comparator/SongTrackNumberComparator.java @@ -0,0 +1,24 @@ +package com.kabouzeid.materialmusic.comparator; + +import com.kabouzeid.materialmusic.model.Song; + +import java.util.Comparator; + +/** + * Created by karim on 25.11.14. + */ +public class SongTrackNumberComparator implements Comparator { + @Override + public int compare(Song lhs, Song rhs) { + // 0 gleich + // -1 steht über dem anderen + // 1 steht unter dem anderen + if (lhs.trackNumber == rhs.trackNumber) { + return 0; + } + if (lhs.trackNumber > rhs.trackNumber) { + return 1; + } + return -1; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/helper/MusicPlayerRemote.java b/app/src/main/java/com/kabouzeid/materialmusic/helper/MusicPlayerRemote.java new file mode 100644 index 00000000..5ca44c36 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/helper/MusicPlayerRemote.java @@ -0,0 +1,271 @@ +package com.kabouzeid.materialmusic.helper; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.interfaces.OnMusicRemoteEventListener; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.MusicRemoteEvent; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.service.MusicService; +import com.kabouzeid.materialmusic.util.InternalStorageUtil; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 29.11.14. + */ +public class MusicPlayerRemote implements OnMusicRemoteEventListener { + private static final String TAG = MusicPlayerRemote.class.getSimpleName(); + + private App app; + + private int position = -1; + + private List playingQueue; + private List onMusicRemoteEventListeners; + + private MusicService musicService; + private Intent musicServiceIntent; + private boolean musicBound = false; + + public MusicPlayerRemote(Context context) { + app = (App) context.getApplicationContext(); + playingQueue = new ArrayList<>(); + onMusicRemoteEventListeners = new ArrayList<>(); + startAndBindService(); + } + + private void startAndBindService() { + if (musicServiceIntent == null) { + musicServiceIntent = new Intent(app, MusicService.class); + app.bindService(musicServiceIntent, musicConnection, Context.BIND_AUTO_CREATE); + app.startService(musicServiceIntent); + } + } + + private ServiceConnection musicConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + MusicService.MusicBinder binder = (MusicService.MusicBinder) service; + musicService = binder.getService(); + musicService.setPosition(position); + musicBound = true; + musicService.addOnMusicRemoteEventListener(MusicPlayerRemote.this); + setPlayingQueue(playingQueue); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SERVICE_CONNECTED); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + musicBound = false; + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SERVICE_DISCONNECTED); + } + }; + + public boolean playSongAt(int position) { + if (musicBound) { + if (position < getPlayingQueue().size() && position >= 0) { + this.position = position; + musicService.setPosition(position); + musicService.playSong(); + return true; + } else { + Log.e(TAG, "No song in queue at given index!"); + } + } + return false; + } + + public void pauseSong() { + if (musicBound) { + musicService.pausePlaying(); + } + } + + public void playNextSong() { + if (musicBound) { + musicService.playNextSong(); + } + } + + public void playPreviousSong() { + if (musicBound) { + musicService.back(); + } + } + + public void back() { + if (musicBound) { + musicService.back(); + } + } + + public boolean isPlaying() { + if (musicBound) { + return musicService.isPlaying(); + } + return false; + } + + public void resumePlaying() { + if (musicBound) { + musicService.resumePlaying(); + } + } + + public long getCurrentSongId() { + if (musicBound) { + return musicService.getCurrentSongId(); + } + try { + return playingQueue.get(position).id; + } catch (Exception e) { + return -1; + } + } + + public int getPosition() { + if (musicBound) { + position = musicService.getPosition(); + } + return position; + } + + public void setPlayingQueue(List songs) { + playingQueue = songs; + if (musicBound) { + musicService.setPlayingQueue(playingQueue); + } + } + + public List getPlayingQueue() { + if (musicBound) { + playingQueue = musicService.getPlayingQueue(); + } + return playingQueue; + } + + public Song getCurrentSong() { + final int position = getPosition(); + if (position != -1) { + return getPlayingQueue().get(position); + } + return new Song(); + } + + public int getSongProgressMillis() { + if (isPlayerPrepared()) { + return musicService.getSongProgressMillis(); + } + return -1; + } + + public int getSongDurationMillis() { + if (isPlayerPrepared()) { + return musicService.getSongDurationMillis(); + } + return -1; + } + + public boolean isMusicBound() { + return musicBound; + } + + public void seekTo(int millis) { + if (musicBound) { + musicService.seekTo(millis); + } + } + + public boolean isPlayerPrepared() { + if (musicBound) { + return musicService.isPlayerPrepared(); + } + return false; + } + + public void notifyPlayingQueueChanged() { + final long currentSongId = getCurrentSongId(); + } + + public int getRepeatMode() { + if (musicBound) { + return musicService.getRepeatMode(); + } + return app.getDefaultSharedPreferences().getInt(AppKeys.SP_REPEAT_MODE, 0); + } + + public int getShuffleMode() { + if (musicBound) { + return musicService.getShuffleMode(); + } + return app.getDefaultSharedPreferences().getInt(AppKeys.SP_SHUFFLE_MODE, 0); + } + + public boolean cycleRepeatMode() { + if (musicBound) { + musicService.cycleRepeatMode(); + return true; + } + return false; + } + + public boolean toggleShuffleMode() { + if (musicBound) { + musicService.toggleShuffle(); + return true; + } + return false; + } + + @Override + public void onMusicRemoteEvent(MusicRemoteEvent event) { + notifyOnMusicRemoteEventListeners(event.getAction()); + } + + public void addOnMusicRemoteEventListener(OnMusicRemoteEventListener onMusicRemoteEventListener) { + onMusicRemoteEventListeners.add(onMusicRemoteEventListener); + } + + public void removeOnMusicRemoteEventListener(OnMusicRemoteEventListener onMusicRemoteEventListener) { + onMusicRemoteEventListeners.remove(onMusicRemoteEventListener); + } + + public void removeAllOnMusicRemoteEventListeners() { + onMusicRemoteEventListeners.clear(); + } + + private void notifyOnMusicRemoteEventListeners(int event) { + MusicRemoteEvent musicRemoteEvent = new MusicRemoteEvent(event); + for (OnMusicRemoteEventListener listener : onMusicRemoteEventListeners) { + listener.onMusicRemoteEvent(musicRemoteEvent); + } + } + + @SuppressWarnings("unchecked") + public void restorePreviousState() { + try { + List restoredQueue = (ArrayList) InternalStorageUtil.readObject(app, AppKeys.IS_PLAYING_QUEUE); + int restoredPosition = (int) InternalStorageUtil.readObject(app, AppKeys.IS_POSITION_IN_QUEUE); + setPlayingQueue(restoredQueue); + position = restoredPosition; + if (musicBound) { + musicService.setPosition(restoredPosition); + } + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STATE_RESTORED); + Log.i(TAG, "restored last state"); + } catch (IOException | ClassNotFoundException | ClassCastException e) { + Log.e(TAG, "error while restoring music service state", e); + playingQueue = new ArrayList<>(); + position = -1; + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/helper/NotificationHelper.java b/app/src/main/java/com/kabouzeid/materialmusic/helper/NotificationHelper.java new file mode 100644 index 00000000..910609b8 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/helper/NotificationHelper.java @@ -0,0 +1,178 @@ +package com.kabouzeid.materialmusic.helper; + +/** + * Created by karim on 27.12.14. + */ + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.TaskStackBuilder; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.support.v4.app.NotificationCompat; +import android.widget.RemoteViews; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.service.MusicService; +import com.kabouzeid.materialmusic.ui.activities.MusicControllerActivity; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.nostra13.universalimageloader.core.ImageLoader; + +public class NotificationHelper { + public static final String TAG = NotificationHelper.class.getSimpleName(); + public static final int NOTIFICATION_ID = 1337; + + private final MusicService service; + + private final NotificationManager notificationManager; + private Notification notification = null; + + private RemoteViews notificationLayout; + private RemoteViews notificationLayoutExpanded; + + public NotificationHelper(final MusicService service) { + this.service = service; + notificationManager = (NotificationManager) service + .getSystemService(Context.NOTIFICATION_SERVICE); + } + + public void buildNotification(Song song, final boolean isPlaying) { + notificationLayout = new RemoteViews(service.getPackageName(), + R.layout.notification_playing); + notificationLayoutExpanded = new RemoteViews(service.getPackageName(), + R.layout.notification_playing_expanded); + + setUpCollapsedLayout(song); + setUpExpandedLayout(song); + setUpPlaybackActions(isPlaying); + setUpExpandedPlaybackActions(isPlaying); + + notification = new NotificationCompat.Builder(service) + .setSmallIcon(R.drawable.notification_icon) + .setContentIntent(getOpenMusicControllerPendingIntent()) + .setCategory(NotificationCompat.CATEGORY_PROGRESS) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContent(notificationLayout) + .build(); + notification.bigContentView = notificationLayoutExpanded; + + service.startForeground(NOTIFICATION_ID, notification); + } + + public void killNotification() { + service.stopForeground(true); + notification = null; + } + + public void updatePlayState(final boolean isPlaying) { + if (notification == null || notificationManager == null) { + return; + } + if (notificationLayout != null) { + notificationLayout.setImageViewResource(R.id.button_toggle_playpause, + isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); + } + if (notificationLayoutExpanded != null) { + notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_playpause, + isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); + } + notificationManager.notify(NOTIFICATION_ID, notification); + } + + private PendingIntent getOpenMusicControllerPendingIntent() { + Intent result = new Intent(service, MusicControllerActivity.class); + TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(service); + taskStackBuilder.addParentStack(MusicControllerActivity.class); + taskStackBuilder.addNextIntent(result); + return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + } + + private void setUpExpandedPlaybackActions(boolean isPlaying) { + notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_toggle_playpause, + retrievePlaybackActions(1)); + + notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_next, + retrievePlaybackActions(2)); + + notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_prev, + retrievePlaybackActions(3)); + + notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_quit, + retrievePlaybackActions(4)); + + notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_playpause, + isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); + } + + private void setUpPlaybackActions(boolean isPlaying) { + notificationLayout.setOnClickPendingIntent(R.id.button_toggle_playpause, + retrievePlaybackActions(1)); + + notificationLayout.setOnClickPendingIntent(R.id.button_next, + retrievePlaybackActions(2)); + + notificationLayout.setOnClickPendingIntent(R.id.button_quit, + retrievePlaybackActions(4)); + + notificationLayout.setImageViewResource(R.id.button_toggle_playpause, + isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); + } + + private PendingIntent retrievePlaybackActions(final int which) { + Intent action; + PendingIntent pendingIntent; + final ComponentName serviceName = new ComponentName(service, MusicService.class); + switch (which) { + case 1: + action = new Intent(MusicService.ACTION_TOGGLE_PLAYBACK); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 1, action, 0); + return pendingIntent; + case 2: + action = new Intent(MusicService.ACTION_SKIP); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 2, action, 0); + return pendingIntent; + case 3: + action = new Intent(MusicService.ACTION_REWIND); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 3, action, 0); + return pendingIntent; + case 4: + action = new Intent(MusicService.ACTION_QUIT); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 4, action, 0); + return pendingIntent; + default: + break; + } + return null; + } + + private void setUpCollapsedLayout(Song song) { + loadAlbumArt(notificationLayout, MusicUtil.getAlbumArtUri(song.albumId).toString()); + notificationLayout.setTextViewText(R.id.song_title, song.title); + notificationLayout.setTextViewText(R.id.song_artist, song.title); + } + + private void setUpExpandedLayout(Song song) { + loadAlbumArt(notificationLayoutExpanded, MusicUtil.getAlbumArtUri(song.albumId).toString()); + notificationLayoutExpanded.setTextViewText(R.id.song_title, song.title); + notificationLayoutExpanded.setTextViewText(R.id.song_artist, song.artistName); + notificationLayoutExpanded.setTextViewText(R.id.album_title, song.albumName); + } + + private static void loadAlbumArt(RemoteViews notificationView, String albumArtUri) { + Bitmap albumArtBitmap = ImageLoader.getInstance().loadImageSync(albumArtUri); + if (albumArtBitmap == null) { + notificationView.setImageViewResource(R.id.album_art, R.drawable.default_album_art); + } else { + notificationView.setImageViewBitmap(R.id.album_art, albumArtBitmap); + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/helper/PlayingQueueDialogHelper.java b/app/src/main/java/com/kabouzeid/materialmusic/helper/PlayingQueueDialogHelper.java new file mode 100644 index 00000000..93523ed4 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/helper/PlayingQueueDialogHelper.java @@ -0,0 +1,50 @@ +package com.kabouzeid.materialmusic.helper; + +import android.content.Context; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.PlayListAdapter; +import com.kabouzeid.materialmusic.model.Song; +import com.mobeta.android.dslv.DragSortListView; + +/** + * Created by karim on 24.01.15. + */ +public class PlayingQueueDialogHelper { + public static MaterialDialog getDialog(Context context) { + final App app = (App) context.getApplicationContext(); + MaterialDialog dialog = new MaterialDialog.Builder(context) + .title(context.getResources().getString(R.string.label_current_playing_queue)) + .customView(R.layout.dialog_playlist, false) + .positiveText(context.getResources().getString(R.string.close)) + .negativeText(context.getResources().getString(R.string.save_as_playlist)) + .callback(new MaterialDialog.ButtonCallback() { + @Override + public void onPositive(MaterialDialog dialog) { + super.onPositive(dialog); + dialog.dismiss(); + } + + @Override + public void onNegative(MaterialDialog dialog) { + super.onNegative(dialog); + } + }) + .build(); + final DragSortListView dragSortListView = (DragSortListView) dialog.getCustomView().findViewById(R.id.dragSortListView); + final PlayListAdapter playListAdapter = new PlayListAdapter(context, app.getMusicPlayerRemote().getPlayingQueue()); + dragSortListView.setAdapter(playListAdapter); + dragSortListView.setDropListener(new DragSortListView.DropListener() { + @Override + public void drop(int from, int to) { + Song songToMove = app.getMusicPlayerRemote().getPlayingQueue().get(from); + app.getMusicPlayerRemote().getPlayingQueue().remove(from); + app.getMusicPlayerRemote().getPlayingQueue().add(to, songToMove); + playListAdapter.notifyDataSetChanged(); + } + }); + return dialog; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/helper/Shuffler.java b/app/src/main/java/com/kabouzeid/materialmusic/helper/Shuffler.java new file mode 100644 index 00000000..56a5de70 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/helper/Shuffler.java @@ -0,0 +1,52 @@ +package com.kabouzeid.materialmusic.helper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by karim on 24.01.15. + */ +public class Shuffler { + private static int MAX_HISTORY_SIZE = 250; + private List order = new ArrayList<>(); + private int position; + private int interval; + + public Shuffler(final int interval) { + order = getShuffledOrderList(interval); + this.interval = interval; + } + + public int nextInt(boolean infinite) { + position = position + 1; + if (position > order.size() - 1) { + if (infinite) { + order.addAll(getShuffledOrderList(interval)); + if (order.size() > Math.max(interval, MAX_HISTORY_SIZE)) { + order = order.subList(order.size() / 2 - 1, order.size() - 1); + } + } else { + return order.get(order.size() - 1); + } + } + return order.get(position); + } + + public int previousInt() { + position = position - 1; + if (position < 0) { + position = 0; + } + return order.get(position); + } + + private List getShuffledOrderList(int interval) { + final List newList = new ArrayList<>(); + for (int i = 0; i < interval; i++) { + newList.add(i); + } + Collections.shuffle(newList); + return newList; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/helper/SongDetailDialogHelper.java b/app/src/main/java/com/kabouzeid/materialmusic/helper/SongDetailDialogHelper.java new file mode 100644 index 00000000..a8485e02 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/helper/SongDetailDialogHelper.java @@ -0,0 +1,84 @@ +package com.kabouzeid.materialmusic.helper; + +import android.content.Context; +import android.text.Html; +import android.text.Spanned; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.kabouzeid.materialmusic.util.Util; + +import org.jaudiotagger.audio.AudioFile; +import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.audio.AudioHeader; +import org.jaudiotagger.audio.exceptions.CannotReadException; +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; +import org.jaudiotagger.tag.TagException; + +import java.io.File; +import java.io.IOException; + +/** + * Created by karim on 19.01.15. + */ +public class SongDetailDialogHelper { + public static final String TAG = SongDetailDialogHelper.class.getSimpleName(); + + public static MaterialDialog getDialog(final Context context, final File songFile) { + MaterialDialog dialog = new MaterialDialog.Builder(context) + .customView(R.layout.dialog_file_details, true) + .title(context.getResources().getString(R.string.label_details)) + .positiveText(context.getResources().getString(R.string.ok)) + .callback(new MaterialDialog.ButtonCallback() { + @Override + public void onPositive(MaterialDialog dialog) { + dialog.dismiss(); + } + }) + .build(); + + View dialogView = dialog.getCustomView(); + final TextView fileName = (TextView) dialogView.findViewById(R.id.file_name); + final TextView filePath = (TextView) dialogView.findViewById(R.id.file_path); + final TextView fileSize = (TextView) dialogView.findViewById(R.id.file_size); + final TextView fileFormat = (TextView) dialogView.findViewById(R.id.file_format); + final TextView trackLength = (TextView) dialogView.findViewById(R.id.track_length); + final TextView bitRate = (TextView) dialogView.findViewById(R.id.bitrate); + final TextView samplingRate = (TextView) dialogView.findViewById(R.id.sampling_rate); + + fileName.setText(makeTextWithTitle(context, R.string.label_file_name, "-")); + filePath.setText(makeTextWithTitle(context, R.string.label_file_path, "-")); + fileSize.setText(makeTextWithTitle(context, R.string.label_file_size, "-")); + fileFormat.setText(makeTextWithTitle(context, R.string.label_file_format, "-")); + trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, "-")); + bitRate.setText(makeTextWithTitle(context, R.string.label_bit_rate, "-")); + samplingRate.setText(makeTextWithTitle(context, R.string.label_sampling_rate, "-")); + + try { + if (songFile != null && songFile.exists()) { + AudioFile audioFile = AudioFileIO.read(songFile); + AudioHeader audioHeader = audioFile.getAudioHeader(); + + fileName.setText(makeTextWithTitle(context, R.string.label_file_name, songFile.getName())); + filePath.setText(makeTextWithTitle(context, R.string.label_file_path, songFile.getAbsolutePath())); + fileSize.setText(makeTextWithTitle(context, R.string.label_file_size, Util.getFileSizeString(songFile.length()))); + fileFormat.setText(makeTextWithTitle(context, R.string.label_file_format, audioHeader.getFormat())); + trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, MusicUtil.getReadableDurationString(audioHeader.getTrackLength() * 1000))); + bitRate.setText(makeTextWithTitle(context, R.string.label_bit_rate, audioHeader.getBitRate() + " kb/s")); + samplingRate.setText(makeTextWithTitle(context, R.string.label_sampling_rate, audioHeader.getSampleRate() + " Hz")); + } + } catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { + Log.e(TAG, "error while reading the song file", e); + } + return dialog; + } + + private static Spanned makeTextWithTitle(Context context, int titleResId, String text) { + return Html.fromHtml("" + context.getResources().getString(titleResId) + ": " + "" + text); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/interfaces/KabSearchAbleFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/interfaces/KabSearchAbleFragment.java new file mode 100644 index 00000000..b74cfe53 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/interfaces/KabSearchAbleFragment.java @@ -0,0 +1,10 @@ +package com.kabouzeid.materialmusic.interfaces; + +/** + * Created by karim on 29.12.14. + */ +public interface KabSearchAbleFragment { + public void search(String query); + + public void returnToNonSearch(); +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/interfaces/KabViewsDisableAble.java b/app/src/main/java/com/kabouzeid/materialmusic/interfaces/KabViewsDisableAble.java new file mode 100644 index 00000000..9703383d --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/interfaces/KabViewsDisableAble.java @@ -0,0 +1,12 @@ +package com.kabouzeid.materialmusic.interfaces; + +/** + * Created by karim on 23.12.14. + */ +public interface KabViewsDisableAble { + public void enableViews(); + + public void disableViews(); + + public boolean areViewsEnabled(); +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/interfaces/OnMusicRemoteEventListener.java b/app/src/main/java/com/kabouzeid/materialmusic/interfaces/OnMusicRemoteEventListener.java new file mode 100644 index 00000000..8d931d2a --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/interfaces/OnMusicRemoteEventListener.java @@ -0,0 +1,10 @@ +package com.kabouzeid.materialmusic.interfaces; + +import com.kabouzeid.materialmusic.model.MusicRemoteEvent; + +/** + * Created by karim on 19.12.14. + */ +public interface OnMusicRemoteEventListener { + public void onMusicRemoteEvent(MusicRemoteEvent event); +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/lastfm/LastFMUtil.java b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/LastFMUtil.java new file mode 100644 index 00000000..c58900a1 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/LastFMUtil.java @@ -0,0 +1,9 @@ +package com.kabouzeid.materialmusic.lastfm; + +/** + * Created by karim on 15.01.15. + */ +public class LastFMUtil { + public static String BASE_URL = "ws.audioscrobbler.com"; + public static String API_KEY = "bd9c6ea4d55ec9ed3af7d276e5ece304"; +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/lastfm/album/LastFMAlbumImageLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/album/LastFMAlbumImageLoader.java new file mode 100644 index 00000000..bcd6e5ee --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/album/LastFMAlbumImageLoader.java @@ -0,0 +1,110 @@ +package com.kabouzeid.materialmusic.lastfm.album; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; +import android.view.View; + +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.JsonObjectRequest; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.provider.AlbumJSONStore; +import com.kabouzeid.materialmusic.util.Util; +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.process.BitmapProcessor; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by karim on 01.01.15. + */ +public class LastFMAlbumImageLoader { + public static final String TAG = LastFMAlbumImageLoader.class.getSimpleName(); + + public static void loadAlbumImage(Context context, String queryAlbum, String queryArtist, AlbumImageLoaderCallback callback) { + if (queryAlbum != null) { + String albumJSON = AlbumJSONStore.getInstance(context).getAlbumJSON(queryAlbum + queryArtist); + if (albumJSON != null) { + Log.i(TAG, queryAlbum + " by " + queryArtist + " is in cache."); + try { + loadAlbumImageFromJSON(new JSONObject(albumJSON), callback); + } catch (JSONException e) { + Log.e(TAG, "Error while parsing string from cache to JSONObject", e); + } + } else { + Log.i(TAG, queryAlbum + " is not in cache."); + downloadAlbumImage(context, queryAlbum, queryArtist, callback); + } + } + } + + private static void loadAlbumImageFromJSON(JSONObject jsonObject, final AlbumImageLoaderCallback callback) { + Log.i(TAG, "Applying album art..."); + String url = LastFMAlbumInfoUtil.getAlbumImageUrlFromJSON(jsonObject); + if (!url.trim().equals("")) { + DisplayImageOptions options = new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(false) + .postProcessor(new BitmapProcessor() { + @Override + public Bitmap process(Bitmap bmp) { + return Util.getAlbumArtScaledBitmap(bmp, true); + } + }) + .build(); + ImageLoader.getInstance().loadImage(url, options, new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) { + + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + callback.onAlbumImageLoaded(null, null); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + callback.onAlbumImageLoaded(loadedImage, imageUri); + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + callback.onAlbumImageLoaded(null, null); + } + }); + } else { + callback.onAlbumImageLoaded(null, null); + } + } + + private static void downloadAlbumImage(final Context context, final String album, final String artist, final AlbumImageLoaderCallback callback) { + Log.i(TAG, "Downloading details for " + album); + App app = (App) context.getApplicationContext(); + String albumUrl = LastFMAlbumInfoUtil.getAlbumUrl(album, artist); + JsonObjectRequest albumInfoJSONRequest = new JsonObjectRequest(0, albumUrl, null, new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + Log.i(TAG, "Download was successful!"); + LastFMAlbumInfoUtil.saveAlbumJSONDataToCacheAndDisk(context, album, artist, response); + loadAlbumImageFromJSON(response, callback); + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Log.e(TAG, "Download failed!", error); + callback.onAlbumImageLoaded(null, null); + } + }); + app.addToRequestQueue(albumInfoJSONRequest); + } + + public static interface AlbumImageLoaderCallback { + public void onAlbumImageLoaded(Bitmap albumImage, String uri); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/lastfm/album/LastFMAlbumInfoUtil.java b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/album/LastFMAlbumInfoUtil.java new file mode 100644 index 00000000..57e19842 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/album/LastFMAlbumInfoUtil.java @@ -0,0 +1,87 @@ +package com.kabouzeid.materialmusic.lastfm.album; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.kabouzeid.materialmusic.lastfm.LastFMUtil; +import com.kabouzeid.materialmusic.provider.AlbumJSONStore; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by karim on 24.12.14. + */ +public class LastFMAlbumInfoUtil { + public static final String TAG = LastFMAlbumInfoUtil.class.getSimpleName(); + + private static String AUTO_CORRECT = "1"; + + public static String getAlbumUrl(String album, String artist) { + if (album != null) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme("http") + .authority(LastFMUtil.BASE_URL) + .appendPath("2.0") + .appendQueryParameter("method", "album.getinfo") + .appendQueryParameter("album", album) + .appendQueryParameter("artist", artist) + //.appendQueryParameter("lang", "de") + .appendQueryParameter("autocorrect", AUTO_CORRECT) + .appendQueryParameter("api_key", LastFMUtil.API_KEY) + .appendQueryParameter("format", "json"); + return builder.build().toString(); + } + return ""; + } + + public static String getAlbumNameFromJSON(JSONObject rootJSON) { + try { + return rootJSON.getJSONObject("album").getString("name"); + } catch (JSONException e) { + //Log.e(TAG, "Error while getting album name from JSON parameter!", e); + return ""; + } + } + + public static String getAlbumThumbnailUrlFromJSON(JSONObject rootJSON) { + try { + JSONArray images = getAlbumImageArrayFromJSON(rootJSON); + if (images.length() > 2) { + return images.getJSONObject(2).getString("#text"); + } else if (images.length() > 1) { + return images.getJSONObject(1).getString("#text"); + } + return images.getJSONObject(0).getString("#text"); + } catch (JSONException | NullPointerException e) { + //Log.e(TAG, "Error while getting album thumbnail image from JSON parameter!", e); + return ""; + } + } + + public static String getAlbumImageUrlFromJSON(JSONObject rootJSON) { + try { + JSONArray images = getAlbumImageArrayFromJSON(rootJSON); + return images.getJSONObject(images.length() - 1).getString("#text"); + } catch (JSONException | NullPointerException e) { + //Log.e(TAG, "Error while getting album image from JSON parameter!", e); + return ""; + } + } + + public static JSONArray getAlbumImageArrayFromJSON(JSONObject rootJSON) { + try { + return rootJSON.getJSONObject("album").getJSONArray("image"); + } catch (JSONException e) { + //Log.e(TAG, "Error while getting album image array from JSON parameter!", e); + return null; + } + } + + public static void saveAlbumJSONDataToCacheAndDisk(Context context, String album, String artist, JSONObject jsonObject) { + Log.i(TAG, "Saving new JSON album data for " + album + "..."); + AlbumJSONStore.getInstance(context).addAlbumJSON(album + artist, jsonObject.toString()); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistBiographyLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistBiographyLoader.java new file mode 100644 index 00000000..7b43751b --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistBiographyLoader.java @@ -0,0 +1,65 @@ +package com.kabouzeid.materialmusic.lastfm.artist; + +import android.content.Context; +import android.util.Log; + +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.JsonObjectRequest; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.provider.ArtistJSONStore; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by karim on 01.01.15. + */ +public class LastFMArtistBiographyLoader { + public static final String TAG = LastFMArtistBiographyLoader.class.getSimpleName(); + + public static void loadArtistBio(Context context, String queryArtist, ArtistBioLoaderCallback callback) { + if (queryArtist != null) { + String artistJSON = ArtistJSONStore.getInstance(context).getArtistJSON(queryArtist); + if (artistJSON != null) { + Log.i(TAG, queryArtist + " is in cache."); + try { + JSONObject json = new JSONObject(artistJSON); + String bio = LastFMArtistInfoUtil.getArtistBiographyFromJSON(json); + callback.onArtistBioLoaded(bio); + } catch (JSONException e) { + Log.e(TAG, "Error while parsing bio from cache to JSONObject", e); + } + } else { + Log.i(TAG, queryArtist + " is not in cache."); + downloadArtistBio(context, queryArtist, callback); + } + } + } + + private static void downloadArtistBio(final Context context, final String artist, final ArtistBioLoaderCallback callback) { + Log.i(TAG, "Downloading details for " + artist); + App app = (App) context.getApplicationContext(); + String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist); + JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + Log.i(TAG, "Download was successful!"); + LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response); + String bio = LastFMArtistInfoUtil.getArtistBiographyFromJSON(response); + callback.onArtistBioLoaded(bio); + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Log.e(TAG, "Download failed!", error); + callback.onArtistBioLoaded(""); + } + }); + app.addToRequestQueue(artistInfoJSONRequest); + } + + public static interface ArtistBioLoaderCallback { + public void onArtistBioLoaded(String bio); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistImageLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistImageLoader.java new file mode 100644 index 00000000..c327d5d1 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistImageLoader.java @@ -0,0 +1,98 @@ +package com.kabouzeid.materialmusic.lastfm.artist; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; +import android.view.View; + +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.JsonObjectRequest; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.provider.ArtistJSONStore; +import com.kabouzeid.materialmusic.util.ImageLoaderUtil; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by karim on 01.01.15. + */ +public class LastFMArtistImageLoader { + public static final String TAG = LastFMArtistImageLoader.class.getSimpleName(); + + public static void loadArtistImage(Context context, String queryArtist, ArtistImageLoaderCallback callback) { + if (queryArtist != null) { + String artistJSON = ArtistJSONStore.getInstance(context).getArtistJSON(queryArtist); + if (artistJSON != null) { + Log.i(TAG, queryArtist + " is in cache."); + try { + loadArtistImageFromJSON(new JSONObject(artistJSON), callback); + } catch (JSONException e) { + Log.e(TAG, "Error while parsing string from cache to JSONObject", e); + } + } else { + Log.i(TAG, queryArtist + " is not in cache."); + downloadArtistImage(context, queryArtist, callback); + } + } + } + + private static void loadArtistImageFromJSON(JSONObject jsonObject, final ArtistImageLoaderCallback callback) { + Log.i(TAG, "Applying artist art..."); + String url = LastFMArtistInfoUtil.getArtistImageUrlFromJSON(jsonObject); + if (!url.trim().equals("")) { + ImageLoader.getInstance().loadImage(url, ImageLoaderUtil.getCacheOnDiskOptions(), new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) { + + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + callback.onArtistImageLoaded(null); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + callback.onArtistImageLoaded(loadedImage); + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + callback.onArtistImageLoaded(null); + } + }); + } else { + callback.onArtistImageLoaded(null); + } + } + + private static void downloadArtistImage(final Context context, final String artist, final ArtistImageLoaderCallback callback) { + Log.i(TAG, "Downloading details for " + artist); + App app = (App) context.getApplicationContext(); + String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist); + JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + Log.i(TAG, "Download was successful!"); + LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response); + loadArtistImageFromJSON(response, callback); + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Log.e(TAG, "Download failed!", error); + callback.onArtistImageLoaded(null); + } + }); + app.addToRequestQueue(artistInfoJSONRequest); + } + + public static interface ArtistImageLoaderCallback { + public void onArtistImageLoaded(Bitmap artistImage); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistInfoUtil.java b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistInfoUtil.java new file mode 100644 index 00000000..e2f3c10a --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistInfoUtil.java @@ -0,0 +1,95 @@ +package com.kabouzeid.materialmusic.lastfm.artist; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.kabouzeid.materialmusic.lastfm.LastFMUtil; +import com.kabouzeid.materialmusic.provider.ArtistJSONStore; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by karim on 24.12.14. + */ +public class LastFMArtistInfoUtil { + public static final String TAG = LastFMArtistInfoUtil.class.getSimpleName(); + + private static String AUTO_CORRECT = "1"; + + public static String getArtistUrl(String artist) { + if (artist != null) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme("http") + .authority(LastFMUtil.BASE_URL) + .appendPath("2.0") + .appendQueryParameter("method", "artist.getinfo") + .appendQueryParameter("artist", artist) + //.appendQueryParameter("lang", "de") + .appendQueryParameter("autocorrect", AUTO_CORRECT) + .appendQueryParameter("api_key", LastFMUtil.API_KEY) + .appendQueryParameter("format", "json"); + return builder.build().toString(); + } + return ""; + } + + public static String getArtistNameFromJSON(JSONObject rootJSON) { + try { + return rootJSON.getJSONObject("artist").getString("name"); + } catch (JSONException e) { + //Log.e(TAG, "Error while getting artist name from JSON parameter!", e); + return ""; + } + } + + public static String getArtistThumbnailUrlFromJSON(JSONObject rootJSON) { + try { + JSONArray images = getArtistImageArrayFromJSON(rootJSON); + if (images.length() > 2) { + return images.getJSONObject(2).getString("#text"); + } else if (images.length() > 1) { + return images.getJSONObject(1).getString("#text"); + } + return images.getJSONObject(0).getString("#text"); + } catch (JSONException | NullPointerException e) { + //Log.e(TAG, "Error while getting artist thumbnail image from JSON parameter!", e); + return ""; + } + } + + public static String getArtistImageUrlFromJSON(JSONObject rootJSON) { + try { + JSONArray images = getArtistImageArrayFromJSON(rootJSON); + return images.getJSONObject(images.length() - 1).getString("#text"); + } catch (JSONException | NullPointerException e) { + //Log.e(TAG, "Error while getting artist image from JSON parameter!", e); + return ""; + } + } + + public static JSONArray getArtistImageArrayFromJSON(JSONObject rootJSON) { + try { + return rootJSON.getJSONObject("artist").getJSONArray("image"); + } catch (JSONException e) { + //Log.e(TAG, "Error while getting artist image array from JSON parameter!", e); + return null; + } + } + + public static String getArtistBiographyFromJSON(JSONObject rootJSON) { + try { + return rootJSON.getJSONObject("artist").getJSONObject("bio").getString("content"); + } catch (JSONException e) { + //Log.e(TAG, "Error while getting artist biografie from JSON parameter!", e); + return ""; + } + } + + public static void saveArtistJSONDataToCacheAndDisk(Context context, String artist, JSONObject jsonObject) { + Log.i(TAG, "Saving new JSON artist data for " + artist + "..."); + ArtistJSONStore.getInstance(context).addArtistJSON(artist, jsonObject.toString()); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistThumbnailLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistThumbnailLoader.java new file mode 100644 index 00000000..d9b54c41 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/lastfm/artist/LastFMArtistThumbnailLoader.java @@ -0,0 +1,98 @@ +package com.kabouzeid.materialmusic.lastfm.artist; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; +import android.view.View; + +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.JsonObjectRequest; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.provider.ArtistJSONStore; +import com.kabouzeid.materialmusic.util.ImageLoaderUtil; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by karim on 01.01.15. + */ +public class LastFMArtistThumbnailLoader { + public static final String TAG = LastFMArtistThumbnailLoader.class.getSimpleName(); + + public static void loadArtistThumbnail(Context context, String queryArtist, ArtistThumbnailLoaderCallback callback) { + if (queryArtist != null) { + String artistJSON = ArtistJSONStore.getInstance(context).getArtistJSON(queryArtist); + if (artistJSON != null) { + Log.i(TAG, queryArtist + " is in cache."); + try { + loadArtistThumbnailFromJSON(new JSONObject(artistJSON), callback); + } catch (JSONException e) { + Log.e(TAG, "Error while parsing string from cache to JSONObject", e); + } + } else { + Log.i(TAG, queryArtist + " is not in cache."); + downloadArtistThumbnail(context, queryArtist, callback); + } + } + } + + private static void loadArtistThumbnailFromJSON(JSONObject jsonObject, final ArtistThumbnailLoaderCallback callback) { + Log.i(TAG, "Applying artist thumbnail..."); + String url = LastFMArtistInfoUtil.getArtistThumbnailUrlFromJSON(jsonObject); + if (!url.trim().equals("")) { + ImageLoader.getInstance().loadImage(url, ImageLoaderUtil.getCacheOnDiskOptions(), new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) { + + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + callback.onArtistThumbnailLoaded(null); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + callback.onArtistThumbnailLoaded(loadedImage); + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + callback.onArtistThumbnailLoaded(null); + } + }); + } else { + callback.onArtistThumbnailLoaded(null); + } + } + + private static void downloadArtistThumbnail(final Context context, final String artist, final ArtistThumbnailLoaderCallback callback) { + Log.i(TAG, "Downloading details for " + artist); + App app = (App) context.getApplicationContext(); + String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist); + JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + Log.i(TAG, "Download was successful!"); + LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response); + loadArtistThumbnailFromJSON(response, callback); + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Log.e(TAG, "Download failed!", error); + callback.onArtistThumbnailLoaded(null); + } + }); + app.addToRequestQueue(artistInfoJSONRequest); + } + + public static interface ArtistThumbnailLoaderCallback { + public void onArtistThumbnailLoaded(Bitmap thumbnail); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/loader/AlbumLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/loader/AlbumLoader.java new file mode 100644 index 00000000..6bd806ad --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/loader/AlbumLoader.java @@ -0,0 +1,106 @@ +package com.kabouzeid.materialmusic.loader; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; + +import com.kabouzeid.materialmusic.model.Album; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 29.12.14. + */ +public class AlbumLoader { + + public static List getAllAlbums(Context context) { + Cursor cursor = makeAlbumCursor(context); + List albums = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + final String albumName = cursor.getString(1); + final String artist = cursor.getString(2); + final int artistId = cursor.getInt(3); + final int songCount = cursor.getInt(4); + final int year = cursor.getInt(5); + + final Album album = new Album(id, albumName, artist, artistId, songCount, year); + albums.add(album); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return albums; + } + + public static Album getAlbum(Context context, int albumId) { + Cursor cursor = makeAlbumCursor(context); + Album album = new Album(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + if (id == albumId) { + final String albumName = cursor.getString(1); + final String artist = cursor.getString(2); + final int artistId = cursor.getInt(3); + final int songCount = cursor.getInt(4); + final int year = cursor.getInt(5); + + album = new Album(id, albumName, artist, artistId, songCount, year); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return album; + } + + public static List getAlbums(Context context, String query) { + Cursor cursor = makeAlbumCursor(context); + List albums = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final String albumName = cursor.getString(1); + if (albumName.trim().toLowerCase().contains(query.trim().toLowerCase())) { + final int id = cursor.getInt(0); + final String artist = cursor.getString(2); + final int artistId = cursor.getInt(3); + final int songCount = cursor.getInt(4); + final int year = cursor.getInt(5); + + final Album album = new Album(id, albumName, artist, artistId, songCount, year); + albums.add(album); + } + } while (cursor.moveToNext()); + } + if (cursor != null) { + cursor.close(); + } + return albums; + } + + private static Cursor makeAlbumCursor(final Context context) { + return context.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.AlbumColumns.ALBUM, + /* 2 */ + MediaStore.Audio.AlbumColumns.ARTIST, + /* 3 */ + MediaStore.Audio.Media.ARTIST_ID, + /* 4 */ + MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS, + /* 5 */ + MediaStore.Audio.AlbumColumns.FIRST_YEAR + }, null, null, null); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/loader/AlbumSongLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/loader/AlbumSongLoader.java new file mode 100644 index 00000000..35d2bb57 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/loader/AlbumSongLoader.java @@ -0,0 +1,65 @@ +package com.kabouzeid.materialmusic.loader; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; + +import com.kabouzeid.materialmusic.model.Song; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 29.12.14. + */ +public class AlbumSongLoader { + + public static List getAlbumSongList(final Context context, final int albumId) { + Cursor cursor = makeAlbumSongCursor(context, albumId); + List songs = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + final String songName = cursor.getString(1); + final String artist = cursor.getString(2); + final String album = cursor.getString(3); + final long duration = cursor.getLong(4); + final int trackNumber = cursor.getInt(5); + final int artistId = cursor.getInt(6); + + final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); + songs.add(song); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return songs; + } + + public static final Cursor makeAlbumSongCursor(final Context context, final int albumId) { + final StringBuilder selection = new StringBuilder(); + selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"); + selection.append(" AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"); + selection.append(" AND " + MediaStore.Audio.AudioColumns.ALBUM_ID + "=" + albumId); + return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.AudioColumns.TITLE, + /* 2 */ + MediaStore.Audio.AudioColumns.ARTIST, + /* 3 */ + MediaStore.Audio.AudioColumns.ALBUM, + /* 4 */ + MediaStore.Audio.AudioColumns.DURATION, + /* 5 */ + MediaStore.Audio.AudioColumns.TRACK, + /* 6 */ + MediaStore.Audio.AudioColumns.ARTIST_ID + }, selection.toString(), null, null); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistAlbumLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistAlbumLoader.java new file mode 100644 index 00000000..ce980ce0 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistAlbumLoader.java @@ -0,0 +1,54 @@ +package com.kabouzeid.materialmusic.loader; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; + +import com.kabouzeid.materialmusic.model.Album; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 04.01.15. + */ +public class ArtistAlbumLoader { + public static List getArtistAlbumList(final Context context, final int artistId) { + Cursor cursor = makeArtistAlbumCursor(context, artistId); + List albums = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + final String albumName = cursor.getString(1); + final String artist = cursor.getString(2); + final int songCount = cursor.getInt(3); + final int year = cursor.getInt(4); + + final Album album = new Album(id, albumName, artist, artistId, songCount, year); + albums.add(album); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return albums; + } + + public static Cursor makeArtistAlbumCursor(final Context context, final int artistId) { + return context.getContentResolver().query( + MediaStore.Audio.Artists.Albums.getContentUri("external", artistId), new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.AlbumColumns.ALBUM, + /* 2 */ + MediaStore.Audio.AlbumColumns.ARTIST, + /* 3 */ + MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS, + /* 4 */ + MediaStore.Audio.AlbumColumns.FIRST_YEAR + }, null, null, null); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistLoader.java new file mode 100644 index 00000000..be950a12 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistLoader.java @@ -0,0 +1,97 @@ +package com.kabouzeid.materialmusic.loader; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; + +import com.kabouzeid.materialmusic.model.Artist; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 29.12.14. + */ +public class ArtistLoader { + + public static List getAllArtists(Context context) { + Cursor cursor = makeArtistCursor(context); + List artists = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + final String artistName = cursor.getString(1); + final int albumCount = cursor.getInt(2); + final int songCount = cursor.getInt(3); + + final Artist artist = new Artist(id, artistName, albumCount, songCount); + artists.add(artist); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return artists; + } + + public static Artist getArtist(Context context, int artistId) { + Cursor cursor = makeArtistCursor(context); + Artist artist = new Artist(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + if (id == artistId) { + final String artistName = cursor.getString(1); + final int albumCount = cursor.getInt(2); + final int songCount = cursor.getInt(3); + + artist = new Artist(id, artistName, albumCount, songCount); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return artist; + } + + public static List getArtists(Context context, String query) { + Cursor cursor = makeArtistCursor(context); + List artists = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final String artistName = cursor.getString(1); + if (artistName.trim().toLowerCase().contains(query.trim().toLowerCase())) { + final int id = cursor.getInt(0); + final int albumCount = cursor.getInt(2); + final int songCount = cursor.getInt(3); + + final Artist artist = new Artist(id, artistName, albumCount, songCount); + artists.add(artist); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return artists; + } + + public static final Cursor makeArtistCursor(final Context context) { + return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.ArtistColumns.ARTIST, + /* 2 */ + MediaStore.Audio.ArtistColumns.NUMBER_OF_ALBUMS, + /* 3 */ + MediaStore.Audio.ArtistColumns.NUMBER_OF_TRACKS + }, null, null, null); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistSongLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistSongLoader.java new file mode 100644 index 00000000..b89cb4ac --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/loader/ArtistSongLoader.java @@ -0,0 +1,64 @@ +package com.kabouzeid.materialmusic.loader; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; + +import com.kabouzeid.materialmusic.model.Song; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 01.01.15. + */ +public class ArtistSongLoader { + public static List getArtistSongList(final Context context, final int artistId) { + Cursor cursor = makeArtistSongCursor(context, artistId); + List songs = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + final String songName = cursor.getString(1); + final String artist = cursor.getString(2); + final String album = cursor.getString(3); + final long duration = cursor.getLong(4); + final int trackNumber = cursor.getInt(5); + final int albumId = cursor.getInt(6); + + final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); + songs.add(song); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return songs; + } + + public static Cursor makeArtistSongCursor(final Context context, final int artistId) { + final StringBuilder selection = new StringBuilder(); + selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"); + selection.append(" AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"); + selection.append(" AND " + MediaStore.Audio.AudioColumns.ARTIST_ID + "=" + artistId); + return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.AudioColumns.TITLE, + /* 2 */ + MediaStore.Audio.AudioColumns.ARTIST, + /* 3 */ + MediaStore.Audio.AudioColumns.ALBUM, + /* 4 */ + MediaStore.Audio.AudioColumns.DURATION, + /* 5 */ + MediaStore.Audio.AudioColumns.TRACK, + /* 6 */ + MediaStore.Audio.AudioColumns.ALBUM_ID + }, selection.toString(), null, null); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/loader/SongFileLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/loader/SongFileLoader.java new file mode 100644 index 00000000..93a61298 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/loader/SongFileLoader.java @@ -0,0 +1,62 @@ +package com.kabouzeid.materialmusic.loader; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 11.01.15. + */ +public class SongFileLoader { + public static final String TAG = SongFileLoader.class.getSimpleName(); + + public static List getSongFiles(Context context, List queryIds) { + Cursor cursor = makeSongFileCursor(context); + List songFiles = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + if (queryIds.contains(id)) { + songFiles.add(cursor.getString(1)); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return songFiles; + } + + public static String getSongFile(Context context, int queryId) { + Cursor cursor = makeSongFileCursor(context); + String filePath = ""; + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + if (id == queryId) { + filePath = cursor.getString(1); + break; + } + } while (cursor.moveToNext()); + } + if (cursor != null) { + cursor.close(); + } + return filePath; + } + + public static final Cursor makeSongFileCursor(final Context context) { + return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.AudioColumns.DATA, + }, null, null, null); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/loader/SongLoader.java b/app/src/main/java/com/kabouzeid/materialmusic/loader/SongLoader.java new file mode 100644 index 00000000..de31dfab --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/loader/SongLoader.java @@ -0,0 +1,117 @@ +package com.kabouzeid.materialmusic.loader; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; + +import com.kabouzeid.materialmusic.model.Song; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 29.12.14. + */ +public class SongLoader { + public static List getAllSongs(Context context) { + Cursor cursor = makeAlbumSongCursor(context); + List songs = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + final String songName = cursor.getString(1); + final String artist = cursor.getString(2); + final String album = cursor.getString(3); + final long duration = cursor.getLong(4); + final int trackNumber = cursor.getInt(5); + final int artistId = cursor.getInt(6); + final int albumId = cursor.getInt(7); + + final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); + songs.add(song); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return songs; + } + + public static List getSongs(Context context, String query) { + Cursor cursor = makeAlbumSongCursor(context); + List songs = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + final String songName = cursor.getString(1); + if (songName.trim().toLowerCase().contains(query.trim().toLowerCase())) { + final int id = cursor.getInt(0); + final String artist = cursor.getString(2); + final String album = cursor.getString(3); + final long duration = cursor.getLong(4); + final int trackNumber = cursor.getInt(5); + final int artistId = cursor.getInt(6); + final int albumId = cursor.getInt(7); + + final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); + songs.add(song); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + return songs; + } + + public static Song getSong(Context context, int queryId) { + Cursor cursor = makeAlbumSongCursor(context); + Song song = null; + if (cursor != null && cursor.moveToFirst()) { + do { + final int id = cursor.getInt(0); + if (id == queryId) { + final String songName = cursor.getString(1); + final String artist = cursor.getString(2); + final String album = cursor.getString(3); + final long duration = cursor.getLong(4); + final int trackNumber = cursor.getInt(5); + final int artistId = cursor.getInt(6); + final int albumId = cursor.getInt(7); + song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); + break; + } + } while (cursor.moveToNext()); + } + if (cursor != null) { + cursor.close(); + } + return song; + } + + public static final Cursor makeAlbumSongCursor(final Context context) { + final StringBuilder selection = new StringBuilder(); + selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"); + return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.AudioColumns.TITLE, + /* 2 */ + MediaStore.Audio.AudioColumns.ARTIST, + /* 3 */ + MediaStore.Audio.AudioColumns.ALBUM, + /* 4 */ + MediaStore.Audio.AudioColumns.DURATION, + /* 5 */ + MediaStore.Audio.AudioColumns.TRACK, + /* 6 */ + MediaStore.Audio.AudioColumns.ARTIST_ID, + /* 7 */ + MediaStore.Audio.AudioColumns.ALBUM_ID + }, selection.toString(), null, null); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/misc/AppKeys.java b/app/src/main/java/com/kabouzeid/materialmusic/misc/AppKeys.java new file mode 100644 index 00000000..b517cd6e --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/misc/AppKeys.java @@ -0,0 +1,23 @@ +package com.kabouzeid.materialmusic.misc; + +/** + * Created by karim on 22.12.14. + */ +public final class AppKeys { + public static final String SP_THEME = "com.kabouzeid.materialmusic.THEME"; + public static final String SP_NAVIGATION_DRAWER_ITEM_POSITION = "com.kabouzeid.materialmusic.NAVIGATION_DRAWER_ITEM_POSITION"; + public static final String SP_USER_LEARNED_DRAWER = "com.kabouzeid.materialmusic.NAVIGATION_DRAWER_LEARNED"; + public static final String SP_ONLY_ON_WIFI = "com.kabouzeid.materialmusic.ONLY_ON_WIFI"; + public static final String SP_SHUFFLE_MODE = "com.kabouzeid.materialmusic.SHUFFLE_MODE"; + public static final String SP_REPEAT_MODE = "com.kabouzeid.materialmusic.REPEAT_MODE"; + + public static final String IS_PLAYING_QUEUE = "com.kabouzeid.materialmusic.PLAYING_QUEUE"; + public static final String IS_POSITION_IN_QUEUE = "com.kabouzeid.materialmusic.POSITION_IN_QUEUE"; + public static final String IS_ARTIST_JSON_INFO_CACHE = "com.kabouzeid.materialmusic.ARTIST_JSON_INFO_CACHE"; + + public static final String E_ALBUM = "com.kabouzeid.materialmusic.ALBUM"; + public static final String E_ARTIST = "com.kabouzeid.materialmusic.ARTIST"; + public static final String E_SONG = "com.kabouzeid.materialmusic.SONG"; + public static final String E_TAG_EDIT_MODE = "com.kabouzeid.materialmusic.TAG_EDIT_MODE"; + public static final String E_ID = "com.kabouzeid.materialmusic.ID"; +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallAnimatorListener.java b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallAnimatorListener.java new file mode 100644 index 00000000..d9dd6242 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallAnimatorListener.java @@ -0,0 +1,26 @@ +package com.kabouzeid.materialmusic.misc; + +/** + * Created by karim on 20.12.14. + */ +public class SmallAnimatorListener implements com.nineoldandroids.animation.Animator.AnimatorListener { + @Override + public void onAnimationStart(com.nineoldandroids.animation.Animator animation) { + + } + + @Override + public void onAnimationEnd(com.nineoldandroids.animation.Animator animation) { + + } + + @Override + public void onAnimationCancel(com.nineoldandroids.animation.Animator animation) { + + } + + @Override + public void onAnimationRepeat(com.nineoldandroids.animation.Animator animation) { + + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallObservableScrollViewCallbacks.java b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallObservableScrollViewCallbacks.java new file mode 100644 index 00000000..a60aca83 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallObservableScrollViewCallbacks.java @@ -0,0 +1,24 @@ +package com.kabouzeid.materialmusic.misc; + +import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; +import com.github.ksoichiro.android.observablescrollview.ScrollState; + +/** + * Created by karim on 20.12.14. + */ +public class SmallObservableScrollViewCallbacks implements ObservableScrollViewCallbacks { + @Override + public void onScrollChanged(int i, boolean b, boolean b2) { + + } + + @Override + public void onDownMotionEvent() { + + } + + @Override + public void onUpOrCancelMotionEvent(ScrollState scrollState) { + + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallOnGestureListener.java b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallOnGestureListener.java new file mode 100644 index 00000000..1153a0da --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallOnGestureListener.java @@ -0,0 +1,39 @@ +package com.kabouzeid.materialmusic.misc; + +import android.view.GestureDetector; +import android.view.MotionEvent; + +/** + * Created by karim on 20.12.14. + */ +public class SmallOnGestureListener implements GestureDetector.OnGestureListener { + @Override + public boolean onDown(MotionEvent e) { + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + return false; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallTransitionListener.java b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallTransitionListener.java new file mode 100644 index 00000000..a9c6af2e --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/misc/SmallTransitionListener.java @@ -0,0 +1,35 @@ +package com.kabouzeid.materialmusic.misc; + +import android.annotation.TargetApi; +import android.transition.Transition; + +/** + * Created by karim on 20.12.14. + */ +@TargetApi(21) +public class SmallTransitionListener implements Transition.TransitionListener { + @Override + public void onTransitionStart(Transition transition) { + + } + + @Override + public void onTransitionEnd(Transition transition) { + + } + + @Override + public void onTransitionCancel(Transition transition) { + + } + + @Override + public void onTransitionPause(Transition transition) { + + } + + @Override + public void onTransitionResume(Transition transition) { + + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/model/Album.java b/app/src/main/java/com/kabouzeid/materialmusic/model/Album.java new file mode 100644 index 00000000..7a703dab --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/model/Album.java @@ -0,0 +1,32 @@ +package com.kabouzeid.materialmusic.model; + +/** + * Created by karim on 22.11.14. + */ +public class Album { + + public int id; + public int artistId; + public String title; + public String artistName; + public int songCount; + public int year; + + public Album(final int id, final String title, final String artistName, final int artistId, + final int songNumber, final int albumYear) { + this.id = id; + this.title = title; + this.artistName = artistName; + this.artistId = artistId; + songCount = songNumber; + year = albumYear; + } + + public Album() { + this.id = -1; + this.title = ""; + this.artistName = ""; + songCount = -1; + year = -1; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/model/Artist.java b/app/src/main/java/com/kabouzeid/materialmusic/model/Artist.java new file mode 100644 index 00000000..1310f656 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/model/Artist.java @@ -0,0 +1,26 @@ +package com.kabouzeid.materialmusic.model; + +/** + * Created by karim on 29.12.14. + */ +public class Artist { + public int id; + public String name; + public int albumCount; + public int songCount; + + public Artist(final int id, final String name, final int songCount, + final int albumCount) { + this.id = id; + this.name = name; + this.songCount = songCount; + this.albumCount = albumCount; + } + + public Artist() { + id = -1; + name = ""; + songCount = -1; + albumCount = -1; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/model/MusicRemoteEvent.java b/app/src/main/java/com/kabouzeid/materialmusic/model/MusicRemoteEvent.java new file mode 100644 index 00000000..a0f31636 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/model/MusicRemoteEvent.java @@ -0,0 +1,35 @@ +package com.kabouzeid.materialmusic.model; + +/** + * Created by karim on 19.12.14. + */ +public class MusicRemoteEvent { + public static final int PLAY = 0; + public static final int PAUSE = 1; + public static final int RESUME = 2; + public static final int STOP = 3; + public static final int NEXT = 4; + public static final int PREV = 5; + + public static final int SONG_COMPLETED = 6; + public static final int QUEUE_COMPLETED = 7; + + public static final int SERVICE_CONNECTED = 8; + public static final int SERVICE_DISCONNECTED = 9; + + public static final int STATE_SAVED = 10; + public static final int STATE_RESTORED = 11; + + public static final int SHUFFLE_MODE_CHANGED = 12; + public static final int REPEAT_MODE_CHANGED = 13; + + private int action; + + public MusicRemoteEvent(int action) { + this.action = action; + } + + public int getAction() { + return action; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/model/NavigationDrawerItem.java b/app/src/main/java/com/kabouzeid/materialmusic/model/NavigationDrawerItem.java new file mode 100644 index 00000000..9c8974ed --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/model/NavigationDrawerItem.java @@ -0,0 +1,14 @@ +package com.kabouzeid.materialmusic.model; + +/** + * Created by karim on 23.11.14. + */ +public class NavigationDrawerItem { + public String title; + public int imageRes; + + public NavigationDrawerItem(String title, int imageRes) { + this.title = title; + this.imageRes = imageRes; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/model/Song.java b/app/src/main/java/com/kabouzeid/materialmusic/model/Song.java new file mode 100644 index 00000000..9ce41d92 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/model/Song.java @@ -0,0 +1,40 @@ +package com.kabouzeid.materialmusic.model; + +import java.io.Serializable; + +/** + * Created by karim on 23.11.14. + */ +public class Song implements Serializable { + public int id; + public int albumId; + public int artistId; + public String title; + public String artistName; + public String albumName; + public long duration; + public int trackNumber; + + public Song(final int id, final int albumId, final int artistId, final String title, final String artistName, + final String albumName, final long duration, final int trackNumber) { + this.id = id; + this.albumId = albumId; + this.artistId = artistId; + this.title = title; + this.artistName = artistName; + this.albumName = albumName; + this.duration = duration; + this.trackNumber = trackNumber; + } + + public Song() { + this.id = -1; + this.albumId = -1; + this.artistId = -1; + this.title = ""; + this.artistName = ""; + this.albumName = ""; + this.duration = -1; + this.trackNumber = -1; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/provider/AlbumJSONStore.java b/app/src/main/java/com/kabouzeid/materialmusic/provider/AlbumJSONStore.java new file mode 100644 index 00000000..9139e105 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/provider/AlbumJSONStore.java @@ -0,0 +1,103 @@ +package com.kabouzeid.materialmusic.provider; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class AlbumJSONStore extends SQLiteOpenHelper { + + private static final int VERSION = 1; + public static final String DATABASE_NAME = "albumJSONLastFM.db"; + private static AlbumJSONStore sInstance = null; + + public AlbumJSONStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + } + + @Override + public void onCreate(final SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + AlbumJSONColumns.NAME + + " (" + AlbumJSONColumns.ALBUMANDARTIST_NAME + " TEXT NOT NULL," + + AlbumJSONColumns.JSON + " TEXT NOT NULL);" + ); + } + + @Override + public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + AlbumJSONColumns.NAME); + onCreate(db); + } + + public static synchronized AlbumJSONStore getInstance(final Context context) { + if (sInstance == null) { + sInstance = new AlbumJSONStore(context.getApplicationContext()); + } + return sInstance; + } + + public void addAlbumJSON(final String albumAndArtistName, final String JSON) { + if (albumAndArtistName == null || JSON == null) { + return; + } + + final SQLiteDatabase database = getWritableDatabase(); + final ContentValues values = new ContentValues(2); + + database.beginTransaction(); + + values.put(AlbumJSONColumns.ALBUMANDARTIST_NAME, albumAndArtistName.trim().toLowerCase()); + values.put(AlbumJSONColumns.JSON, JSON); + + database.insert(AlbumJSONColumns.NAME, null, values); + database.setTransactionSuccessful(); + database.endTransaction(); + } + + public String getAlbumJSON(final String albumAndArtistName) { + if (albumAndArtistName == null) { + return null; + } + + final SQLiteDatabase database = getReadableDatabase(); + final String[] projection = new String[]{ + AlbumJSONColumns.JSON, + AlbumJSONColumns.ALBUMANDARTIST_NAME + }; + final String selection = AlbumJSONColumns.ALBUMANDARTIST_NAME + "=?"; + final String[] having = new String[]{ + albumAndArtistName.trim().toLowerCase() + }; + Cursor cursor = database.query(AlbumJSONColumns.NAME, projection, selection, having, null, + null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final String JSON = cursor.getString(cursor.getColumnIndexOrThrow(AlbumJSONColumns.JSON)); + cursor.close(); + return JSON; + } + if (cursor != null) { + cursor.close(); + } + return null; + } + + public static void deleteDatabase(final Context context) { + context.deleteDatabase(DATABASE_NAME); + } + + public void removeItem(final String albumAndArtistName) { + final SQLiteDatabase database = getReadableDatabase(); + database.delete(AlbumJSONColumns.NAME, AlbumJSONColumns.ALBUMANDARTIST_NAME + " = ?", new String[]{ + albumAndArtistName.trim().toLowerCase() + }); + + } + + public interface AlbumJSONColumns { + public static final String NAME = "AlbumJSON"; + public static final String ALBUMANDARTIST_NAME = "AlbumAndArtistName"; + public static final String JSON = "JSON"; + } + +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/provider/ArtistJSONStore.java b/app/src/main/java/com/kabouzeid/materialmusic/provider/ArtistJSONStore.java new file mode 100644 index 00000000..ae9575e0 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/provider/ArtistJSONStore.java @@ -0,0 +1,103 @@ +package com.kabouzeid.materialmusic.provider; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class ArtistJSONStore extends SQLiteOpenHelper { + + private static final int VERSION = 1; + public static final String DATABASE_NAME = "artistJSONLastFM.db"; + private static ArtistJSONStore sInstance = null; + + public ArtistJSONStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + } + + @Override + public void onCreate(final SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + ArtistJSONColumns.NAME + + " (" + ArtistJSONColumns.ARTIST_NAME + " TEXT NOT NULL," + + ArtistJSONColumns.JSON + " TEXT NOT NULL);" + ); + } + + @Override + public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + ArtistJSONColumns.NAME); + onCreate(db); + } + + public static synchronized ArtistJSONStore getInstance(final Context context) { + if (sInstance == null) { + sInstance = new ArtistJSONStore(context.getApplicationContext()); + } + return sInstance; + } + + public void addArtistJSON(final String artistName, final String JSON) { + if (artistName == null || JSON == null) { + return; + } + + final SQLiteDatabase database = getWritableDatabase(); + final ContentValues values = new ContentValues(2); + + database.beginTransaction(); + + values.put(ArtistJSONColumns.ARTIST_NAME, artistName.trim().toLowerCase()); + values.put(ArtistJSONColumns.JSON, JSON); + + database.insert(ArtistJSONColumns.NAME, null, values); + database.setTransactionSuccessful(); + database.endTransaction(); + } + + public String getArtistJSON(final String artistName) { + if (artistName == null) { + return null; + } + + final SQLiteDatabase database = getReadableDatabase(); + final String[] projection = new String[]{ + ArtistJSONColumns.JSON, + ArtistJSONColumns.ARTIST_NAME + }; + final String selection = ArtistJSONColumns.ARTIST_NAME + "=?"; + final String[] having = new String[]{ + artistName.trim().toLowerCase() + }; + Cursor cursor = database.query(ArtistJSONColumns.NAME, projection, selection, having, null, + null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final String JSON = cursor.getString(cursor.getColumnIndexOrThrow(ArtistJSONColumns.JSON)); + cursor.close(); + return JSON; + } + if (cursor != null) { + cursor.close(); + } + return null; + } + + public static void deleteDatabase(final Context context) { + context.deleteDatabase(DATABASE_NAME); + } + + public void removeItem(final String artistName) { + final SQLiteDatabase database = getReadableDatabase(); + database.delete(ArtistJSONColumns.NAME, ArtistJSONColumns.ARTIST_NAME + " = ?", new String[]{ + artistName.trim().toLowerCase() + }); + + } + + public interface ArtistJSONColumns { + public static final String NAME = "ArtistJSON"; + public static final String ARTIST_NAME = "ArtistName"; + public static final String JSON = "JSON"; + } + +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/service/MediaButtonIntentReceiver.java b/app/src/main/java/com/kabouzeid/materialmusic/service/MediaButtonIntentReceiver.java new file mode 100644 index 00000000..84f02d87 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/service/MediaButtonIntentReceiver.java @@ -0,0 +1,70 @@ +package com.kabouzeid.materialmusic.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.view.KeyEvent; + +public class MediaButtonIntentReceiver extends BroadcastReceiver { + public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName(); + + private static final int DOUBLE_CLICK = 500; + private static long mLastClickTime = 0; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) { + Log.i(TAG, intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT).toString()); + final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + if (event == null) + return; + final int keycode = event.getKeyCode(); + final int action = event.getAction(); + final long eventTime = event.getEventTime(); + + String command = null; + switch (keycode) { + case KeyEvent.KEYCODE_MEDIA_STOP: + command = MusicService.ACTION_STOP; + break; + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY: + command = MusicService.ACTION_TOGGLE_PLAYBACK; + break; + case KeyEvent.KEYCODE_MEDIA_NEXT: + command = MusicService.ACTION_SKIP; + break; + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + command = MusicService.ACTION_REWIND; + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + command = MusicService.ACTION_PAUSE; + break; + } + if (command != null) { + if (action == KeyEvent.ACTION_DOWN) { + if (event.getRepeatCount() == 0) { + /** + * If another app received the broadcast first, this if statement will skip. + */ + //TODO triple click to rewind + final Intent i = new Intent(context, MusicService.class); + if (keycode == KeyEvent.KEYCODE_HEADSETHOOK + && eventTime - mLastClickTime < DOUBLE_CLICK) { + i.setAction(MusicService.ACTION_SKIP); + mLastClickTime = 0; + } else { + i.setAction(command); + mLastClickTime = eventTime; + } + context.startService(i); + } + } + if (isOrderedBroadcast()) + abortBroadcast(); + } + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/service/MusicService.java b/app/src/main/java/com/kabouzeid/materialmusic/service/MusicService.java new file mode 100644 index 00000000..f1d28b60 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/service/MusicService.java @@ -0,0 +1,630 @@ +package com.kabouzeid.materialmusic.service; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Bitmap; +import android.media.AudioManager; +import android.media.MediaMetadataRetriever; +import android.media.MediaPlayer; +import android.media.RemoteControlClient; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.helper.NotificationHelper; +import com.kabouzeid.materialmusic.helper.Shuffler; +import com.kabouzeid.materialmusic.interfaces.OnMusicRemoteEventListener; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.MusicRemoteEvent; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.util.InternalStorageUtil; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.nostra13.universalimageloader.core.ImageLoader; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +public class MusicService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, AudioManager.OnAudioFocusChangeListener { + private static final String TAG = MusicService.class.getSimpleName(); + + public static final String ACTION_TOGGLE_PLAYBACK = "com.kabouzeid.materialmusic.action.TOGGLE_PLAYBACK"; + public static final String ACTION_PLAY = "com.kabouzeid.materialmusic.action.PLAY"; + public static final String ACTION_PAUSE = "com.kabouzeid.materialmusic.action.PAUSE"; + public static final String ACTION_STOP = "com.kabouzeid.materialmusic.action.STOP"; + public static final String ACTION_SKIP = "com.kabouzeid.materialmusic.action.SKIP"; + public static final String ACTION_REWIND = "com.kabouzeid.materialmusic.action.REWIND"; + public static final String ACTION_QUIT = "com.kabouzeid.materialmusic.action.QUIT"; + + public static final int SHUFFLE_MODE_NONE = 0; + public static final int SHUFFLE_MODE_SHUFFLE = 1; + + public static final int REPEAT_MODE_NONE = 0; + public static final int REPEAT_MODE_ALL = 1; + public static final int REPEAT_MODE_THIS = 2; + + private MediaPlayer player; + private List playingQueue; + private LinkedList playingHistory; + private List onMusicRemoteEventListeners; + private int currentSongId = -1; + private int position = -1; + private int shuffleMode; + private int repeatMode; + private final IBinder musicBind = new MusicBinder(); + private boolean isPlayerPrepared; + private boolean wasPlayingBeforeFocusLoss; + private boolean thingsRegistered; + private NotificationHelper notificationHelper; + private AudioManager audioManager; + private RemoteControlClient remoteControlClient; + private Shuffler shuffler; + + private final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + pausePlaying(); + } + } + }; + + private AudioManager getAudioManager() { + if (audioManager == null) { + audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + } + return audioManager; + } + + public MusicService() { + } + + @Override + public void onCreate() { + super.onCreate(); + isPlayerPrepared = false; + playingQueue = new ArrayList<>(); + playingHistory = new LinkedList<>(); + onMusicRemoteEventListeners = new ArrayList<>(); + notificationHelper = new NotificationHelper(this); + + shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_SHUFFLE_MODE, 0); + repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_REPEAT_MODE, 0); + + registerEverything(); + } + + private Shuffler getShuffler() { + if (shuffler == null) { + shuffler = new Shuffler(playingQueue.size()); + } + return shuffler; + } + + private boolean requestFocus() { + int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + + return (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + } + + private void initRemoteControlClient() { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); + PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + remoteControlClient = new RemoteControlClient(mediaPendingIntent); + remoteControlClient.setTransportControlFlags( + RemoteControlClient.FLAG_KEY_MEDIA_PLAY | + RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | + RemoteControlClient.FLAG_KEY_MEDIA_NEXT | + RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS); + getAudioManager().registerRemoteControlClient(remoteControlClient); + } + + private void registerEverything() { + if (!thingsRegistered) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + registerReceiver(receiver, intentFilter); + getAudioManager().registerMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); + initRemoteControlClient(); + thingsRegistered = true; + } + } + + private void unregisterEverything() { + if (thingsRegistered) { + unregisterReceiver(receiver); + getAudioManager().unregisterRemoteControlClient(remoteControlClient); + getAudioManager().unregisterMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); + thingsRegistered = false; + } + } + + private void updateRemoteControlClient(Song song) { + Bitmap loadedImage = ImageLoader.getInstance().loadImageSync(MusicUtil.getAlbumArtUri(song.albumId).toString()); + remoteControlClient + .editMetadata(false) + .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, song.artistName) + .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title) + .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, song.duration) + .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, loadedImage) + .apply(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + setUpMediaPlayerIfNeeded(); + if (intent != null) { + if (intent.getAction() != null) { + String action = intent.getAction(); + switch (action) { + case ACTION_TOGGLE_PLAYBACK: + if (isPlaying()) { + pausePlaying(); + } else { + resumePlaying(); + } + break; + case ACTION_PAUSE: + pausePlaying(); + break; + case ACTION_PLAY: + playSong(); + break; + case ACTION_REWIND: + back(); + break; + case ACTION_SKIP: + playNextSong(); + break; + case ACTION_STOP: + stopPlaying(); + break; + case ACTION_QUIT: + killEverythingAndReleaseResources(); + } + } + } + return START_NOT_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + Log.i(TAG, "onBind"); + return musicBind; + } + + @Override + public boolean onUnbind(Intent intent) { + unregisterEverything(); + killEverythingAndReleaseResources(); + return false; + } + + @Override + public void onCompletion(MediaPlayer mp) { + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SONG_COMPLETED); + if (isLastTrack()) { + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.QUEUE_COMPLETED); + notificationHelper.updatePlayState(isPlaying()); + remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP); + } else { + playNextSong(); + } + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + isPlayerPrepared = false; + player.reset(); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP); + return false; + } + + @Override + public void onPrepared(MediaPlayer mp) { + player.start(); + isPlayerPrepared = true; + notificationHelper.updatePlayState(isPlaying()); + remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.PLAY); + savePosition(); + } + + @Override + public void onDestroy() { + unregisterEverything(); + killEverythingAndReleaseResources(); + } + + private void killEverythingAndReleaseResources() { + savePosition(); + saveQueue(); + stopPlaying(); + notificationHelper.killNotification(); + stopSelf(); + } + + private void setUpMediaPlayerIfNeeded() { + if (player == null) { + player = new MediaPlayer(); + + player.setOnPreparedListener(this); + player.setOnCompletionListener(this); + player.setOnErrorListener(this); + + player.setAudioStreamType(AudioManager.STREAM_MUSIC); + player.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); + } + } + + private void updateNotification() { + notificationHelper.buildNotification(playingQueue.get(position), isPlaying()); + } + + public void setPlayingQueue(List songs) { + if (!playingQueue.equals(songs)) { + this.playingQueue = songs; + shuffler = new Shuffler(playingQueue.size()); + saveQueue(); + } + } + + public List getPlayingQueue() { + return playingQueue; + } + + public void setPosition(int position) { + this.position = position; + } + + public int getPosition() { + return position; + } + + public long getCurrentSongId() { + return currentSongId; + } + + @Override + public void onAudioFocusChange(int focusChange) { + switch (focusChange) { + case AudioManager.AUDIOFOCUS_GAIN: + // resume playback + registerEverything(); + player.setVolume(1.0f, 1.0f); + if (wasPlayingBeforeFocusLoss) { + resumePlaying(); + updateRemoteControlClient(getPlayingQueue().get(position)); + } + updateRemoteControlClient(getPlayingQueue().get(position)); + break; + + case AudioManager.AUDIOFOCUS_LOSS: + // Lost focus for an unbounded amount of time: stop playback and release media player + //TODO maybe also release player + wasPlayingBeforeFocusLoss = isPlaying(); + pausePlaying(); + unregisterEverything(); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + // Lost focus for a short time, but we have to stop + // playback. We don't release the media player because playback + // is likely to resume + wasPlayingBeforeFocusLoss = isPlaying(); + pausePlaying(); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // Lost focus for a short time, but it's ok to keep playing + // at an attenuated level + player.setVolume(0.2f, 0.2f); + break; + } + } + + public class MusicBinder extends Binder { + public MusicService getService() { + return MusicService.this; + } + } + + public void playSong() { + if (requestFocus()) { + setUpMediaPlayerIfNeeded(); + registerEverything(); + isPlayerPrepared = false; + player.reset(); + Uri trackUri = getCurrentPositionTrackUri(); + try { + player.setDataSource(getApplicationContext(), trackUri); + currentSongId = playingQueue.get(position).id; + updateNotification(); + updateRemoteControlClient(getPlayingQueue().get(position)); + player.prepareAsync(); + } catch (Exception e) { + Log.e("MUSIC SERVICE", "Error setting data source", e); + player.reset(); + Toast.makeText(getApplicationContext(), getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP); + notificationHelper.updatePlayState(false); + remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); + } + } else { + Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show(); + } + } + + public void pausePlaying() { + if (isPlaying()) { + player.pause(); + notificationHelper.updatePlayState(isPlaying()); + remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.PAUSE); + } + } + + public void resumePlaying() { + if (requestFocus()) { + if (isPlayerPrepared) { + player.start(); + notificationHelper.updatePlayState(isPlaying()); + remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.RESUME); + } else { + playSong(); + } + } else { + Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show(); + } + } + + public void stopPlaying() { + isPlayerPrepared = false; + player.stop(); + notificationHelper.updatePlayState(isPlaying()); + remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); + player.release(); + player = null; + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP); + } + + public void playNextSong() { + if (position != -1) { + if (isPlayerPrepared) { + + setPosition(getNextPosition()); + playSong(); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.NEXT); + } + } + } + + public void playPreviousSong() { + if (position != -1) { + setPosition(getPreviousPosition()); + playSong(); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.PREV); + } + } + + public void back() { + if (position != -1) { + if (getSongProgressMillis() > 2000) { + seekTo(0); + } else { + playPreviousSong(); + } + } + } + + public int getNextPosition() { + int position = 0; + switch (repeatMode) { + case REPEAT_MODE_NONE: + switch (shuffleMode) { + case SHUFFLE_MODE_NONE: + position = getPosition() + 1; + if (isLastTrack()) { + position -= 1; + } + break; + case SHUFFLE_MODE_SHUFFLE: + position = getShuffler().nextInt(false); + break; + } + break; + case REPEAT_MODE_ALL: + switch (shuffleMode) { + case SHUFFLE_MODE_NONE: + position = getPosition() + 1; + if (isLastTrack()) { + position = 0; + } + break; + case SHUFFLE_MODE_SHUFFLE: + position = getShuffler().nextInt(true); + break; + } + break; + case REPEAT_MODE_THIS: + position = getPosition(); + break; + } + return position; + } + + public int getPreviousPosition() { + int position = 0; + switch (repeatMode) { + case REPEAT_MODE_NONE: + switch (shuffleMode) { + case SHUFFLE_MODE_NONE: + position = getPosition() - 1; + if (position < 0) { + position = 0; + } + break; + case SHUFFLE_MODE_SHUFFLE: + position = getShuffler().previousInt(); + break; + } + break; + case REPEAT_MODE_ALL: + switch (shuffleMode) { + case SHUFFLE_MODE_NONE: + position = getPosition() - 1; + if (position < 0) { + position = getPlayingQueue().size() - 1; + } + break; + case SHUFFLE_MODE_SHUFFLE: + position = getShuffler().previousInt(); + break; + } + break; + case REPEAT_MODE_THIS: + position = getPosition(); + break; + } + return position; + } + + public boolean isPlaying() { + return player != null && player.isPlaying(); + } + + private Uri getCurrentPositionTrackUri() { + return ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, playingQueue.get(position).id); + } + + public int getSongProgressMillis() { + return player.getCurrentPosition(); + } + + public int getSongDurationMillis() { + return player.getDuration(); + } + + public void seekTo(int millis) { + player.seekTo(millis); + } + + public boolean isPlayerPrepared() { + if (player == null) { + return false; + } + return isPlayerPrepared; + } + + private boolean isLastTrack() { + return getPosition() == getPlayingQueue().size() - 1; + } + + private void notifyOnMusicRemoteEventListeners(int event) { + MusicRemoteEvent musicRemoteEvent = new MusicRemoteEvent(event); + for (OnMusicRemoteEventListener listener : onMusicRemoteEventListeners) { + listener.onMusicRemoteEvent(musicRemoteEvent); + } + } + + public void addOnMusicRemoteEventListener(OnMusicRemoteEventListener onMusicRemoteEventListener) { + onMusicRemoteEventListeners.add(onMusicRemoteEventListener); + } + + public void saveQueue() { + try { + InternalStorageUtil.writeObject(MusicService.this, AppKeys.IS_PLAYING_QUEUE, getPlayingQueue()); + Log.i(TAG, "saved current queue state"); + } catch (IOException e) { + Log.e(TAG, "error while saving music service queue state", e); + } + } + + public void savePosition() { + new Thread(new Runnable() { + @Override + public void run() { + try { + InternalStorageUtil.writeObject(MusicService.this, AppKeys.IS_POSITION_IN_QUEUE, getPosition()); + Log.i(TAG, "saved current position state"); + } catch (IOException e) { + Log.e(TAG, "error while saving music service position state", e); + } + } + }).start(); + } + + public void setShuffleMode(final int shuffleMode) { + switch (shuffleMode) { + case SHUFFLE_MODE_SHUFFLE: + shuffler = new Shuffler(getPlayingQueue().size()); + case SHUFFLE_MODE_NONE: + this.shuffleMode = shuffleMode; + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putInt(AppKeys.SP_SHUFFLE_MODE, shuffleMode) + .apply(); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SHUFFLE_MODE_CHANGED); + break; + } + } + + public void setRepeatMode(final int repeatMode) { + switch (repeatMode) { + case REPEAT_MODE_NONE: + case REPEAT_MODE_ALL: + case REPEAT_MODE_THIS: + this.repeatMode = repeatMode; + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putInt(AppKeys.SP_REPEAT_MODE, repeatMode) + .apply(); + notifyOnMusicRemoteEventListeners(MusicRemoteEvent.REPEAT_MODE_CHANGED); + break; + } + } + + public void cycleRepeatMode() { + switch (repeatMode) { + case REPEAT_MODE_NONE: + setRepeatMode(REPEAT_MODE_ALL); + break; + case REPEAT_MODE_ALL: + setRepeatMode(REPEAT_MODE_THIS); + break; + default: + setRepeatMode(REPEAT_MODE_NONE); + break; + } + } + + public void toggleShuffle() { + if (shuffleMode == SHUFFLE_MODE_NONE) { + setShuffleMode(SHUFFLE_MODE_SHUFFLE); + } else { + setShuffleMode(SHUFFLE_MODE_NONE); + } + } + + public int getRepeatMode() { + return repeatMode; + } + + public int getShuffleMode() { + return shuffleMode; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/AlbumDetailActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/AlbumDetailActivity.java new file mode 100644 index 00000000..6fd41021 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/AlbumDetailActivity.java @@ -0,0 +1,491 @@ +package com.kabouzeid.materialmusic.ui.activities; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.support.v7.graphics.Palette; +import android.support.v7.widget.Toolbar; +import android.transition.Transition; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.TextView; + +import com.github.ksoichiro.android.observablescrollview.ObservableListView; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.songadapter.SongAdapter; +import com.kabouzeid.materialmusic.comparator.SongTrackNumberComparator; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.interfaces.OnMusicRemoteEventListener; +import com.kabouzeid.materialmusic.loader.AlbumLoader; +import com.kabouzeid.materialmusic.loader.AlbumSongLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.misc.SmallObservableScrollViewCallbacks; +import com.kabouzeid.materialmusic.model.Album; +import com.kabouzeid.materialmusic.model.MusicRemoteEvent; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.ui.activities.base.AbsFabActivity; +import com.kabouzeid.materialmusic.ui.activities.tageditor.AlbumTagEditorActivity; +import com.kabouzeid.materialmusic.util.ImageLoaderUtil; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.kabouzeid.materialmusic.util.Util; +import com.kabouzeid.materialmusic.util.ViewUtil; +import com.melnykov.fab.FloatingActionButton; +import com.nhaarman.listviewanimations.appearance.AnimationAdapter; +import com.nhaarman.listviewanimations.appearance.simple.ScaleInAnimationAdapter; +import com.nineoldandroids.view.ViewHelper; +import com.nineoldandroids.view.ViewPropertyAnimator; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; + +import java.util.Collections; +import java.util.List; + +/* +* +* A lot of hackery is done in this activity. Changing things may will brake the whole activity. +* +* Should be kinda stable ONLY AS IT IS!!! +* +* */ + +public class AlbumDetailActivity extends AbsFabActivity implements OnMusicRemoteEventListener, KabViewsDisableAble { + public static final String TAG = AlbumDetailActivity.class.getSimpleName(); + + private static final boolean TOOLBAR_IS_STICKY = true; + private static final int DEFAULT_DELAY_NO_TRANSITION = 200; + private static final int DEFAULT_DELAY = 450; + private static final int DEFAULT_ANIMATION_TIME = 1000; + + private App app; + + private Album album; + + private AnimationAdapter animatedSongsAdapter; + private ObservableListView absSongListView; + private View statusBar; + private ImageView albumArtImageView; + private View albumArtOverlayView; + private View songsBackgroundView; + private TextView albumTitleView; + private FloatingActionButton fab; + private Toolbar toolbar; + private int toolbarHeight; + private int headerOffset; + private int titleViewHeight; + private int albumArtViewHeight; + private int toolbarColor; + + private Bitmap albumCover; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + app = (App) getApplicationContext(); + setTheme(app.getAppTheme()); + setUpTranslucence(); + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_album_detail); + + Bundle intentExtras = getIntent().getExtras(); + int albumId = -1; + if (intentExtras != null) { + albumId = intentExtras.getInt(AppKeys.E_ALBUM); + } + album = AlbumLoader.getAlbum(this, albumId); + if (album.id == -1) { + finish(); + } + + initViews(); + setUpObservableListViewParams(); + setUpToolBar(); + setUpViews(); + lollipopTransitionImageWrongSizeFix(); + animateEnterActivity(); + } + + @Override + public void onResume() { + super.onResume(); + enableViews(); + updateFabIcon(); + app.getMusicPlayerRemote().addOnMusicRemoteEventListener(this); + } + + @Override + protected void onStop() { + super.onStop(); + app.getMusicPlayerRemote().removeOnMusicRemoteEventListener(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_album_detail, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + super.onBackPressed(); + return true; + case R.id.action_settings: + return true; + case R.id.action_current_playing: + return openCurrentPlayingIfPossible(null); + case R.id.action_tag_editor: + Intent intent = new Intent(this, AlbumTagEditorActivity.class); + intent.putExtra(AppKeys.E_ID, album.id); + startActivity(intent); + return true; + case R.id.action_go_to_artist: + goToArtistDetailsActivity(album.artistId, null); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void initViews() { + albumArtImageView = (ImageView) findViewById(R.id.album_art); + toolbar = (Toolbar) findViewById(R.id.toolbar); + albumArtOverlayView = findViewById(R.id.overlay); + absSongListView = (ObservableListView) findViewById(R.id.list); + albumTitleView = (TextView) findViewById(R.id.album_title); + fab = (FloatingActionButton) findViewById(R.id.fab); + songsBackgroundView = findViewById(R.id.list_background); + statusBar = findViewById(R.id.statusBar); + } + + private void setUpObservableListViewParams() { + albumArtViewHeight = getResources().getDimensionPixelSize(R.dimen.header_image_height); + toolbarColor = Util.resolveColor(this, R.attr.colorPrimary); + toolbarHeight = Util.getActionBarSize(this); + titleViewHeight = getResources().getDimensionPixelSize(R.dimen.title_view_height); + headerOffset = toolbarHeight; + headerOffset += getResources().getDimensionPixelSize(R.dimen.statusMargin); + } + + private void setUpViews() { + albumTitleView.setText(album.title); + ViewHelper.setAlpha(albumArtOverlayView, 0); + + prepareViewsForOpenAnimation(); + setUpAlbumArtAndApplyPalette(); + setUpListView(); + } + + private void prepareViewsForOpenAnimation() { + albumTitleView.setPivotY(0); + albumTitleView.setScaleY(0); + } + + private void setUpAlbumArtAndApplyPalette() { + ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(album.id).toString(), albumArtImageView, new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) { + albumArtImageView.setImageResource(R.drawable.default_album_art); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + albumArtImageView.setImageResource(R.drawable.default_album_art); + } + + @Override + public void onLoadingComplete(String imageUri, final View view, Bitmap loadedImage) { + albumCover = loadedImage; + applyPalette(loadedImage); + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + albumArtImageView.setImageResource(R.drawable.default_album_art); + } + }); + } + + private void setUpSongsAdapter() { + final List songs = AlbumSongLoader.getAlbumSongList(this, album.id); + Collections.sort(songs, new SongTrackNumberComparator()); + final SongAdapter songAdapter = new SongAdapter(this, this, songs); + +// SwingBottomInAnimationAdapter songsAdapter = new SwingBottomInAnimationAdapter(songAdapter); +// SwingRightInAnimationAdapter songsAdapter = new SwingRightInAnimationAdapter(songAdapter); +// SwingLeftInAnimationAdapter songsAdapter = new SwingLeftInAnimationAdapter(songAdapter); + ScaleInAnimationAdapter songsAdapter = new ScaleInAnimationAdapter(songAdapter); +// AlphaInAnimationAdapter songsAdapter = new AlphaInAnimationAdapter(songAdapter); + + animatedSongsAdapter = songsAdapter; + animatedSongsAdapter.setAbsListView(absSongListView); + + absSongListView.setAdapter(animatedSongsAdapter); + absSongListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (position > 0) { + app.getMusicPlayerRemote().setPlayingQueue(songs); + app.getMusicPlayerRemote().playSongAt(position - 1); + } + } + }); + } + + private void setUpListView() { + absSongListView.setScrollViewCallbacks(observableScrollViewCallbacks); + setListViewPadding(); + final View contentView = getWindow().getDecorView().findViewById(android.R.id.content); + contentView.post(new Runnable() { + @Override + public void run() { + songsBackgroundView.getLayoutParams().height = contentView.getHeight(); + observableScrollViewCallbacks.onScrollChanged(0, false, false); + } + }); + } + + private void setListViewPadding() { + setListViewPaddingTop(); + if (app.isInPortraitMode() || app.isTablet()) { + setListViewPaddingBottom(); + } + } + + private void setListViewPaddingTop() { + final View paddingView = new View(AlbumDetailActivity.this); + AbsListView.LayoutParams lp = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, + albumArtViewHeight + titleViewHeight); + paddingView.setLayoutParams(lp); + paddingView.setClickable(true); + absSongListView.addHeaderView(paddingView); + } + + private void setListViewPaddingBottom() { + final View paddingView = new View(AlbumDetailActivity.this); + AbsListView.LayoutParams lp = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, + Util.getNavigationBarHeight(this)); + paddingView.setLayoutParams(lp); + paddingView.setClickable(true); + absSongListView.addFooterView(paddingView); + } + + private void setUpToolBar() { + setSupportActionBar(toolbar); + getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + if (!TOOLBAR_IS_STICKY) { + toolbar.setBackgroundColor(Color.TRANSPARENT); + } + } + + private void applyPalette(Bitmap bitmap) { + Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + Palette.Swatch swatch = palette.getVibrantSwatch(); + if (swatch != null) { + toolbarColor = swatch.getRgb(); + albumArtOverlayView.setBackgroundColor(swatch.getRgb()); + albumTitleView.setBackgroundColor(swatch.getRgb()); + albumTitleView.setTextColor(swatch.getTitleTextColor()); + } + } + }); + } + + private SmallObservableScrollViewCallbacks observableScrollViewCallbacks = new SmallObservableScrollViewCallbacks() { + @Override + public void onScrollChanged(int scrollY, boolean b, boolean b2) { + super.onScrollChanged(scrollY, b, b2); + // Translate overlay and image + float flexibleRange = albumArtViewHeight - headerOffset; + int minOverlayTransitionY = headerOffset - albumArtOverlayView.getHeight(); + ViewHelper.setTranslationY(albumArtOverlayView, Math.max(minOverlayTransitionY, Math.min(0, -scrollY))); + ViewHelper.setTranslationY(albumArtImageView, Math.max(minOverlayTransitionY, Math.min(0, -scrollY / 2))); + + // Translate list background + ViewHelper.setTranslationY(songsBackgroundView, Math.max(0, -scrollY + albumArtViewHeight)); + + // Change alpha of overlay + ViewHelper.setAlpha(albumArtOverlayView, Math.max(0, Math.min(1, (float) scrollY / flexibleRange))); + + // Translate name text + int maxTitleTranslationY = albumArtViewHeight; + int titleTranslationY = maxTitleTranslationY - scrollY; + if (TOOLBAR_IS_STICKY) { + titleTranslationY = Math.max(headerOffset, titleTranslationY); + } + ViewHelper.setTranslationY(albumTitleView, titleTranslationY); + + // Translate FAB + int fabTranslationY = titleTranslationY + titleViewHeight - (fab.getHeight() / 2); + ViewHelper.setTranslationY(fab, fabTranslationY); + + if (TOOLBAR_IS_STICKY) { + // Change alpha of toolbar background + if (-scrollY + albumArtViewHeight <= headerOffset) { + ViewUtil.setBackgroundAlpha(toolbar, 1, toolbarColor); + ViewUtil.setBackgroundAlpha(statusBar, 1, toolbarColor); + + } else { + ViewUtil.setBackgroundAlpha(toolbar, 0, toolbarColor); + ViewUtil.setBackgroundAlpha(statusBar, 0, toolbarColor); + } + } else { + // Translate Toolbar + if (scrollY < albumArtViewHeight) { + ViewHelper.setTranslationY(toolbar, 0); + } else { + ViewHelper.setTranslationY(toolbar, -scrollY); + } + } + } + }; + + @Override + public void onMusicRemoteEvent(MusicRemoteEvent event) { + switch (event.getAction()) { + case MusicRemoteEvent.PLAY: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_pause_white_48dp)); + break; + case MusicRemoteEvent.PAUSE: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_play_arrow_white_48dp)); + break; + case MusicRemoteEvent.RESUME: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_pause_white_48dp)); + break; + case MusicRemoteEvent.STOP: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_play_arrow_white_48dp)); + break; + case MusicRemoteEvent.QUEUE_COMPLETED: + fab.setImageResource(R.drawable.ic_play_arrow_white_48dp); + break; + } + } + + @Override + public void enableViews() { + super.enableViews(); + absSongListView.setEnabled(true); + fab.setEnabled(true); + toolbar.setEnabled(true); + } + + @Override + public void disableViews() { + super.disableViews(); + absSongListView.setEnabled(false); + fab.setEnabled(false); + toolbar.setEnabled(false); + } + + private void setUpTranslucence() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Util.setStatusBarTranslucent(getWindow(), true); + if (app.isInPortraitMode() || app.isTablet()) { + Util.setNavBarTranslucent(getWindow(), true); + } + } + } + + private void updateFabIcon() { + if (app.getMusicPlayerRemote().isPlaying()) { + fab.setImageResource(R.drawable.ic_pause_white_48dp); + } else { + fab.setImageResource(R.drawable.ic_play_arrow_white_48dp); + } + } + + private void animateHeader(int startDelay) { + ViewPropertyAnimator.animate(albumTitleView) + .scaleX(1) + .scaleY(1) + .setInterpolator(new DecelerateInterpolator(4)) + .setDuration(DEFAULT_ANIMATION_TIME) + .setStartDelay(startDelay) + .start(); + } + + private void animateFab(int startDelay) { + ViewPropertyAnimator.animate(fab) + .scaleX(1) + .scaleY(1) + .setInterpolator(new DecelerateInterpolator(4)) + .setDuration(DEFAULT_ANIMATION_TIME) + .setStartDelay(startDelay) + .start(); + } + + private void animateEnterActivity() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + animateHeader(0); + setUpSongsAdapter(); + } + }, DEFAULT_DELAY); + + } else { + setUpSongsAdapter(); + fab.setScaleX(0); + fab.setScaleY(0); + animateHeader(DEFAULT_DELAY_NO_TRANSITION); + animateFab(DEFAULT_DELAY_NO_TRANSITION); + } + } + + @Override + public void goToAlbum(int albumId) { + if (album.id != albumId) { + goToAlbum(albumId); + } + } + + private void lollipopTransitionImageWrongSizeFix() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().getSharedElementEnterTransition().addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + + } + + @Override + public void onTransitionEnd(Transition transition) { + if (albumCover == null) { + ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(album.id).toString(), albumArtImageView, new ImageLoaderUtil.defaultAlbumArtOnFailed()); + } else { + albumArtImageView.setImageBitmap(albumCover); + } + } + + @Override + public void onTransitionCancel(Transition transition) { + + } + + @Override + public void onTransitionPause(Transition transition) { + + } + + @Override + public void onTransitionResume(Transition transition) { + + } + }); + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/ArtistDetailActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/ArtistDetailActivity.java new file mode 100644 index 00000000..3838e198 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/ArtistDetailActivity.java @@ -0,0 +1,490 @@ +package com.kabouzeid.materialmusic.ui.activities; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.support.v13.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.graphics.Palette; +import android.support.v7.widget.Toolbar; +import android.transition.Transition; +import android.util.SparseArray; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.TextView; + +import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; +import com.github.ksoichiro.android.observablescrollview.ScrollState; +import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.interfaces.OnMusicRemoteEventListener; +import com.kabouzeid.materialmusic.lastfm.artist.LastFMArtistImageLoader; +import com.kabouzeid.materialmusic.loader.ArtistLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.Artist; +import com.kabouzeid.materialmusic.ui.activities.base.AbsFabActivity; +import com.kabouzeid.materialmusic.ui.fragments.artistviewpager.AbsViewPagerTabArtistListFragment; +import com.kabouzeid.materialmusic.ui.fragments.artistviewpager.ViewPagerTabArtistAlbumFragment; +import com.kabouzeid.materialmusic.ui.fragments.artistviewpager.ViewPagerTabArtistBioFragment; +import com.kabouzeid.materialmusic.ui.fragments.artistviewpager.ViewPagerTabArtistSongListFragment; +import com.kabouzeid.materialmusic.util.Util; +import com.kabouzeid.materialmusic.util.ViewUtil; +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.view.ViewHelper; +import com.nineoldandroids.view.ViewPropertyAnimator; + +/* +* +* A lot of hackery is done in this activity. Changing things may will brake the whole activity. +* +* Should be kinda stable ONLY AS IT IS!!! +* +* */ + +public class ArtistDetailActivity extends AbsFabActivity implements OnMusicRemoteEventListener, KabViewsDisableAble, ObservableScrollViewCallbacks { + public static final String TAG = ArtistDetailActivity.class.getSimpleName(); + + public static final String ARG_ARTIST_ID = "com.kabouzeid.materialmusic.artist.id"; + public static final String ARG_ARTIST_NAME = "com.kabouzeid.materialmusic.artist.name"; + + private static final boolean TOOLBAR_IS_STICKY = true; + + private boolean isAnimating; + + private Artist artist; + + private SlidingTabLayout slidingTabs; + private View statusBar; + private ImageView artistImageView; + private View artistArtOverlayView; + private View absAlbumListBackgroundView; + private TextView artistTitleText; + private Toolbar toolbar; + private ViewPager viewPager; + private NavigationAdapter navigationAdapter; + private int toolbarHeight; + private int headerOffset; + private int titleViewHeight; + private int artistImageViewHeight; + private int toolbarColor; + private int tabHeight; + + private Bitmap artistImage; + + private Fragment currentFragment; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + setUpTranslucence(true, true); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_artist_detail); + + getIntentExtras(); + initViews(); + setUpObservableListViewParams(); + setUpToolBar(); + setUpViews(); + lollipopTransitionImageWrongSizeFix(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_artist_detail, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + super.onBackPressed(); + return true; + case R.id.action_settings: + return true; + case R.id.action_current_playing: + openCurrentPlayingIfPossible(null); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void initViews() { + artistImageView = (ImageView) findViewById(R.id.artist_image); + toolbar = (Toolbar) findViewById(R.id.toolbar); + artistArtOverlayView = findViewById(R.id.overlay); + artistTitleText = (TextView) findViewById(R.id.artist_name); + absAlbumListBackgroundView = findViewById(R.id.list_background); + statusBar = findViewById(R.id.statusBar); + slidingTabs = (SlidingTabLayout) findViewById(R.id.sliding_tabs); + } + + private void setUpObservableListViewParams() { + artistImageViewHeight = getResources().getDimensionPixelSize(R.dimen.header_image_height); + toolbarColor = Util.resolveColor(this, R.attr.colorPrimary); + toolbarHeight = Util.getActionBarSize(this); + titleViewHeight = getResources().getDimensionPixelSize(R.dimen.title_view_height); + headerOffset = toolbarHeight; + headerOffset += getResources().getDimensionPixelSize(R.dimen.statusMargin); + tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height); + } + + private void setUpViews() { + artistTitleText.setText(artist.name); + ViewHelper.setAlpha(artistArtOverlayView, 0); + + setUpArtistImageAndApplyPalette(); + setUpViewPatch(); + setUpSlidingTabs(); + } + + private void setUpSlidingTabs() { + navigationAdapter = new NavigationAdapter(this, artist); + viewPager = (ViewPager) findViewById(R.id.pager); + viewPager.setOffscreenPageLimit(2); + viewPager.setAdapter(navigationAdapter); + viewPager.setCurrentItem(1); + + slidingTabs.setViewPager(viewPager); + slidingTabs.setDistributeEvenly(true); + slidingTabs.setCustomTabView(R.layout.tab_indicator, android.R.id.text1); + slidingTabs.setSelectedIndicatorColors(Util.resolveColor(this, R.attr.colorAccent)); + slidingTabs.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + currentFragment = navigationAdapter.getItemAt(position); + if (currentFragment instanceof AbsViewPagerTabArtistListFragment) { + restoreY(((AbsViewPagerTabArtistListFragment) currentFragment).getY()); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + } + + private void setUpArtistImageAndApplyPalette() { + if (artistImage == null) { + LastFMArtistImageLoader.loadArtistImage(this, artist.name, new LastFMArtistImageLoader.ArtistImageLoaderCallback() { + @Override + public void onArtistImageLoaded(Bitmap artistImage) { + if (artistImage != null) { + ArtistDetailActivity.this.artistImage = artistImage; + artistImageView.setImageBitmap(artistImage); + applyPalette(artistImage); + } + } + }); + } else { + artistImageView.setImageBitmap(artistImage); + applyPalette(artistImage); + } + } + + private void setUpViewPatch() { + final View contentView = getWindow().getDecorView().findViewById(android.R.id.content); + contentView.post(new Runnable() { + @Override + public void run() { + absAlbumListBackgroundView.getLayoutParams().height = contentView.getHeight(); + } + }); + } + + private void setUpToolBar() { + setSupportActionBar(toolbar); + getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + if (!TOOLBAR_IS_STICKY) { + toolbar.setBackgroundColor(Color.TRANSPARENT); + } + } + + private void applyPalette(Bitmap bitmap) { + Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + Palette.Swatch swatch = palette.getVibrantSwatch(); + if (swatch != null) { + toolbarColor = swatch.getRgb(); + artistArtOverlayView.setBackgroundColor(swatch.getRgb()); + artistTitleText.setBackgroundColor(swatch.getRgb()); + slidingTabs.setBackgroundColor(swatch.getRgb()); + artistTitleText.setTextColor(swatch.getTitleTextColor()); + } + } + }); + } + + @Override + public void enableViews() { + super.enableViews(); + viewPager.setEnabled(true); + toolbar.setEnabled(true); + } + + @Override + public void disableViews() { + super.disableViews(); + viewPager.setEnabled(false); + toolbar.setEnabled(false); + } + + private void getIntentExtras() { + Bundle intentExtras = getIntent().getExtras(); + final int artistId = intentExtras.getInt(AppKeys.E_ARTIST); + artist = ArtistLoader.getArtist(this, artistId); + if (artist == null) { + finish(); + } + } + + @Override + public void onScrollChanged(int scrollY, boolean b, boolean b2) { + if (!isAnimating) { + int titleTranslationY = getTitleTranslation(scrollY); + ViewHelper.setTranslationY(artistArtOverlayView, getOverlayTranslation(scrollY)); + ViewHelper.setTranslationY(artistImageView, getImageViewTranslation(scrollY)); + ViewHelper.setTranslationY(absAlbumListBackgroundView, getListBackgroundTranslation(scrollY)); + ViewHelper.setAlpha(artistArtOverlayView, getOverlayAlpha(scrollY)); + ViewHelper.setTranslationY(artistTitleText, titleTranslationY); + ViewHelper.setTranslationY(slidingTabs, titleTranslationY); + ViewHelper.setTranslationY(getFab(), getFabTranslation(scrollY)); + translateToolBar(scrollY); + } + } + + private int getImageViewTranslation(int scrollY) { + int minOverlayTransitionY = headerOffset - artistArtOverlayView.getHeight(); + return Math.max(minOverlayTransitionY, Math.min(0, -scrollY / 2)); + } + + private int getOverlayTranslation(int scrollY) { + int minOverlayTransitionY = headerOffset - artistArtOverlayView.getHeight(); + return Math.max(minOverlayTransitionY, Math.min(0, -scrollY)); + } + + private int getListBackgroundTranslation(int scrollY) { + return Math.max(0, -scrollY + artistImageViewHeight); + } + + private int getTitleTranslation(int scrollY) { + int maxTitleTranslationY = artistImageViewHeight; + int titleTranslationY = maxTitleTranslationY - scrollY; + if (TOOLBAR_IS_STICKY) { + titleTranslationY = Math.max(headerOffset, titleTranslationY); + } + return titleTranslationY; + } + + private int getFabTranslation(int scrollY) { + return getTitleTranslation(scrollY) + titleViewHeight + tabHeight - (getFab().getHeight() / 2); + } + + private float getOverlayAlpha(int scrollY) { + float flexibleRange = artistImageViewHeight - headerOffset; + return Math.max(0, Math.min(1, (float) scrollY / flexibleRange)); + } + + private void translateToolBar(int scrollY) { + if (TOOLBAR_IS_STICKY) { + // Change alpha of toolbar background + if (-scrollY + artistImageViewHeight <= headerOffset) { + ViewUtil.setBackgroundAlpha(toolbar, 1, toolbarColor); + ViewUtil.setBackgroundAlpha(statusBar, 1, toolbarColor); + + } else { + ViewUtil.setBackgroundAlpha(toolbar, 0, toolbarColor); + ViewUtil.setBackgroundAlpha(statusBar, 0, toolbarColor); + } + } else { + // Translate Toolbar + if (scrollY < artistImageViewHeight) { + ViewHelper.setTranslationY(toolbar, 0); + } else { + ViewHelper.setTranslationY(toolbar, -scrollY); + } + } + } + + public void restoreY(final int scrollY) { + translateToolBar(scrollY); + int animationTime = 1000; + DecelerateInterpolator interpolator = new DecelerateInterpolator(4); + int titleTranslationY = getTitleTranslation(scrollY); + ViewPropertyAnimator.animate(artistArtOverlayView).y(getOverlayTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start(); + ViewPropertyAnimator.animate(artistImageView).y(getImageViewTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start(); + ViewPropertyAnimator.animate(absAlbumListBackgroundView).y(getListBackgroundTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start(); + ViewPropertyAnimator.animate(artistArtOverlayView).alpha(getOverlayAlpha(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start(); + ViewPropertyAnimator.animate(slidingTabs).y(titleTranslationY + titleViewHeight).setDuration(animationTime).setInterpolator(interpolator).start(); + ViewPropertyAnimator.animate(artistTitleText).y(titleTranslationY).setDuration(animationTime).setInterpolator(interpolator).start(); + ViewPropertyAnimator.animate(getFab()).y(getFabTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + isAnimating = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + translateToolBar(scrollY); + isAnimating = false; + if (currentFragment instanceof AbsViewPagerTabArtistListFragment) { + onScrollChanged((((AbsViewPagerTabArtistListFragment) currentFragment).getY()), false, false); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + isAnimating = false; + } + + @Override + public void onAnimationRepeat(Animator animation) { + isAnimating = true; + } + }).start(); + } + + @Override + public void onDownMotionEvent() { + } + + @Override + public void onUpOrCancelMotionEvent(ScrollState scrollState) { + } + + @Override + public void goToArtist(int artistId) { + if (artist.id != artistId) { + super.goToArtist(artistId); + } + } + + private void lollipopTransitionImageWrongSizeFix() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().getSharedElementEnterTransition().addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + + } + + @Override + public void onTransitionEnd(Transition transition) { + if (artistImage == null) { + LastFMArtistImageLoader.loadArtistImage(ArtistDetailActivity.this, artist.name, new LastFMArtistImageLoader.ArtistImageLoaderCallback() { + @Override + public void onArtistImageLoaded(Bitmap artistImage) { + if (artistImage != null) { + artistImageView.setImageBitmap(artistImage); + } + } + }); + } else { + artistImageView.setImageBitmap(artistImage); + } + } + + @Override + public void onTransitionCancel(Transition transition) { + + } + + @Override + public void onTransitionPause(Transition transition) { + + } + + @Override + public void onTransitionResume(Transition transition) { + + } + }); + } + } + + private static class NavigationAdapter extends FragmentPagerAdapter { + + private String[] titles; + + private SparseArray mPages; + private Artist artist; + private Context context; + + public NavigationAdapter(Activity activity, Artist artist) { + super(activity.getFragmentManager()); + this.artist = artist; + mPages = new SparseArray<>(); + context = activity; + titles = new String[]{ + context.getResources().getString(R.string.tab_songs), + context.getResources().getString(R.string.tab_albums), + context.getResources().getString(R.string.tab_biography) + }; + } + + @Override + public Fragment getItem(int position) { + Bundle args = new Bundle(); + args.putInt(ARG_ARTIST_ID, artist.id); + args.putString(ARG_ARTIST_NAME, artist.name); + Fragment f; + switch (position) { + case 1: + f = mPages.get(position, new ViewPagerTabArtistAlbumFragment()); + break; + case 0: + f = mPages.get(position, new ViewPagerTabArtistSongListFragment()); + break; + case 2: + f = mPages.get(position, new ViewPagerTabArtistBioFragment()); + break; + default: + f = mPages.get(position, new MainActivity.PlaceholderFragment()); + break; + } + f.setArguments(args); + mPages.put(position, f); + return f; + } + + public Fragment getItemAt(int position) { + return mPages.get(position, null); + } + + @Override + public int getCount() { + return titles.length; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + if (0 <= mPages.indexOfKey(position)) { + mPages.remove(position); + } + super.destroyItem(container, position, object); + } + + @Override + public CharSequence getPageTitle(int position) { + return titles[position]; + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/MainActivity.java new file mode 100644 index 00000000..93237258 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/MainActivity.java @@ -0,0 +1,320 @@ +package com.kabouzeid.materialmusic.ui.activities; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.view.MenuItemCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.widget.SearchView; +import android.support.v7.widget.Toolbar; +import android.transition.Explode; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.interfaces.KabSearchAbleFragment; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.interfaces.OnMusicRemoteEventListener; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.ui.activities.base.AbsFabActivity; +import com.kabouzeid.materialmusic.ui.fragments.NavigationDrawerFragment; +import com.kabouzeid.materialmusic.ui.fragments.mainactivityfragments.AlbumViewFragment; +import com.kabouzeid.materialmusic.ui.fragments.mainactivityfragments.ArtistViewFragment; +import com.kabouzeid.materialmusic.ui.fragments.mainactivityfragments.SongViewFragment; +import com.kabouzeid.materialmusic.util.ImageLoaderUtil; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.kabouzeid.materialmusic.util.Util; +import com.kabouzeid.materialmusic.util.ViewUtil; +import com.nostra13.universalimageloader.core.ImageLoader; + + +public class MainActivity extends AbsFabActivity + implements NavigationDrawerFragment.NavigationDrawerCallbacks, OnMusicRemoteEventListener, KabViewsDisableAble { + public static final String TAG = MainActivity.class.getSimpleName(); + + private int currentFragmentPosition = -1; + + private DrawerLayout drawerLayout; + private ActionBarDrawerToggle drawerToggle; + private NavigationDrawerFragment navigationDrawerFragment; + private CharSequence toolbarTitle; + private Toolbar toolbar; + private View statusBar; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setUpTranslucence(true, true); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + initViews(); + setUpToolBar(); + + navigationDrawerFragment.setUp( + R.id.navigation_drawer, + drawerLayout + ); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setExitTransition(new Explode()); + } + } + + @Override + protected void onResume() { + super.onResume(); + updateNavigationDrawerHeader(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getApp().getMusicPlayerRemote().removeAllOnMusicRemoteEventListeners(); + } + + @Override + public void onNavigationDrawerItemSelected(int position) { + if (position == NavigationDrawerFragment.NAVIGATION_DRAWER_HEADER) { + openCurrentPlayingIfPossible(null); + } else { + setFragment(position); + } + } + + private void setFragment(int position) { + if (currentFragmentPosition != position) { + switch (position) { + case 0: + if (getApp().MainActivityFragments[position] == null) { + getApp().MainActivityFragments[position] = new SongViewFragment(); + } + toolbarTitle = getString(R.string.all_songs); + break; + case 1: + if (getApp().MainActivityFragments[position] == null) { + getApp().MainActivityFragments[position] = new AlbumViewFragment(); + } + toolbarTitle = getString(R.string.albums); + break; + case 2: + if (getApp().MainActivityFragments[position] == null) { + getApp().MainActivityFragments[position] = new ArtistViewFragment(); + } + toolbarTitle = getString(R.string.artists); + break; + case 3: + if (getApp().MainActivityFragments[position] == null) { + getApp().MainActivityFragments[position] = new PlaceholderFragment(); + } + toolbarTitle = getString(R.string.genres); + break; + case 4: + if (getApp().MainActivityFragments[position] == null) { + getApp().MainActivityFragments[position] = new PlaceholderFragment(); + } + toolbarTitle = getString(R.string.playlists); + break; + default: + toolbarTitle = getString(R.string.app_name); + return; + } + FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.beginTransaction() + .replace(R.id.container, getApp().MainActivityFragments[position]) + .commit(); + currentFragmentPosition = position; + supportInvalidateOptionsMenu(); + } + } + + private void initViews() { + drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + navigationDrawerFragment = (NavigationDrawerFragment) + getFragmentManager().findFragmentById(R.id.navigation_drawer); + updateNavigationDrawerHeader(); + } + + public void restoreActionBar() { + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(true); + actionBar.setTitle(toolbarTitle); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.drawer, menu); + restoreActionBar(); + + final MenuItem search = menu.findItem(R.id.action_search); + search.setVisible(currentFragmentPosition != -1 && getApp().MainActivityFragments[currentFragmentPosition] instanceof KabSearchAbleFragment); + + + SearchView searchView = (SearchView) MenuItemCompat.getActionView(search); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + if (currentFragmentPosition != -1 && getApp().MainActivityFragments[currentFragmentPosition] instanceof KabSearchAbleFragment) { + ((KabSearchAbleFragment) getApp().MainActivityFragments[currentFragmentPosition]).search(newText); + } + return false; + } + }); + MenuItemCompat.setOnActionExpandListener(search, new MenuItemCompat.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + if (currentFragmentPosition != -1 && getApp().MainActivityFragments[currentFragmentPosition] instanceof KabSearchAbleFragment) { + ((KabSearchAbleFragment) getApp().MainActivityFragments[currentFragmentPosition]).returnToNonSearch(); + } + return true; + } + }); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (drawerToggle.onOptionsItemSelected(item)) { + return true; + } + int id = item.getItemId(); + switch (id) { + case R.id.action_settings: + return true; + case R.id.action_current_playing: + openCurrentPlayingIfPossible(null); + return true; + + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + drawerToggle.onConfigurationChanged(newConfig); + super.onConfigurationChanged(newConfig); + } + + @Override + public void onBackPressed() { + if (navigationDrawerFragment.isDrawerOpen()) { + drawerLayout.closeDrawers(); + return; + } + super.onBackPressed(); + } + + private void setUpToolBar() { + toolbarTitle = getTitle(); + toolbar = (Toolbar) findViewById(R.id.toolbar); + statusBar = findViewById(R.id.statusBar); + setSupportActionBar(toolbar); + ViewUtil.setBackgroundAlpha(toolbar, 0.97f, Util.resolveColor(this, R.attr.colorPrimary)); + ViewUtil.setBackgroundAlpha(statusBar, 0.97f, Util.resolveColor(this, R.attr.colorPrimary)); + setUpDrawerToggle(); + } + + private void setUpDrawerToggle() { + drawerToggle = new ActionBarDrawerToggle( + this, + drawerLayout, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ); + drawerLayout.post(new Runnable() { + @Override + public void run() { + drawerToggle.syncState(); + } + }); + drawerLayout.setDrawerListener(drawerToggle); + } + + private void updateNavigationDrawerHeader() { + Song song = getApp().getMusicPlayerRemote().getCurrentSong(); + if (navigationDrawerFragment != null && song.id != -1) { + ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(song.albumId).toString(), navigationDrawerFragment.getAlbumArtImageView(), new ImageLoaderUtil.defaultAlbumArtOnFailed()); + navigationDrawerFragment.getSongTitle().setText(song.title); + navigationDrawerFragment.getSongArtist().setText(song.artistName); + } + } + + private void disableFragmentViews() { + if (currentFragmentPosition >= 0 && currentFragmentPosition < getApp().MainActivityFragments.length) { + if (getApp().MainActivityFragments[currentFragmentPosition] instanceof KabViewsDisableAble) { + ((KabViewsDisableAble) getApp().MainActivityFragments[currentFragmentPosition]).disableViews(); + } + } + } + + private void enableFragmentViews() { + if (currentFragmentPosition >= 0 && currentFragmentPosition < getApp().MainActivityFragments.length) { + if (getApp().MainActivityFragments[currentFragmentPosition] instanceof KabViewsDisableAble) { + ((KabViewsDisableAble) getApp().MainActivityFragments[currentFragmentPosition]).enableViews(); + } + } + } + + private boolean areFragmentViewsEnabled() { + if (currentFragmentPosition >= 0 && currentFragmentPosition < getApp().MainActivityFragments.length) { + if (getApp().MainActivityFragments[currentFragmentPosition] instanceof KabViewsDisableAble) { + return ((KabViewsDisableAble) getApp().MainActivityFragments[currentFragmentPosition]).areViewsEnabled(); + } + } + return true; + } + + @Override + public void enableViews() { + try { + super.enableViews(); + toolbar.setEnabled(true); + } catch (NullPointerException e) { + Log.e(TAG, "wasn't able to enable the views", e.fillInStackTrace()); + } + } + + @Override + public void disableViews() { + try { + super.disableViews(); + toolbar.setEnabled(false); + } catch (NullPointerException e) { + Log.e(TAG, "wasn't able to disable the views", e.fillInStackTrace()); + } + } + + public static class PlaceholderFragment extends Fragment { + + public PlaceholderFragment() { + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_place_holder, container, false); + return rootView; + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/MusicControllerActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/MusicControllerActivity.java new file mode 100644 index 00000000..ee82bcf3 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/MusicControllerActivity.java @@ -0,0 +1,428 @@ +package com.kabouzeid.materialmusic.ui.activities; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.util.Pair; +import android.support.v7.graphics.Palette; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.helper.PlayingQueueDialogHelper; +import com.kabouzeid.materialmusic.helper.SongDetailDialogHelper; +import com.kabouzeid.materialmusic.interfaces.OnMusicRemoteEventListener; +import com.kabouzeid.materialmusic.lastfm.artist.LastFMArtistImageLoader; +import com.kabouzeid.materialmusic.loader.SongFileLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.MusicRemoteEvent; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.service.MusicService; +import com.kabouzeid.materialmusic.ui.activities.base.AbsFabActivity; +import com.kabouzeid.materialmusic.ui.activities.tageditor.SongTagEditorActivity; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.kabouzeid.materialmusic.util.Util; +import com.kabouzeid.materialmusic.util.ViewUtil; +import com.nineoldandroids.view.ViewPropertyAnimator; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; + +import java.io.File; + +public class MusicControllerActivity extends AbsFabActivity implements OnMusicRemoteEventListener { + public static final String TAG = MusicControllerActivity.class.getSimpleName(); + + private static final int DEFAULT_DELAY = 350; + private static final int DEFAULT_ANIMATION_TIME = 1000; + + private Song song; + private ImageView albumArt; + private ImageView artistArt; + private TextView songTitle; + private TextView songArtist; + private TextView currentSongProgress; + private TextView totalSongDuration; + private View footer; + private SeekBar progressSlider; + private ImageButton nextButton; + private ImageButton prevButton; + private ImageButton repeatButton; + private ImageButton shuffleButton; + + private int lastFooterColor = -1; + + private boolean killThreads = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setUpTranslucence(true, false); + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_music_controller); + + initViews(); + + moveSeekBarIntoPlace(); + + updateCurrentSong(); + + setUpMusicControllers(); + + prepareViewsForOpenAnimation(); + + setUpToolBar(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_title_playing, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + super.onBackPressed(); + return true; + case R.id.action_playing_queue: + final MaterialDialog materialDialog = PlayingQueueDialogHelper.getDialog(this); + materialDialog.show(); + return true; + case R.id.action_tag_editor: + Intent intent = new Intent(this, SongTagEditorActivity.class); + intent.putExtra(AppKeys.E_ID, song.id); + startActivity(intent); + return true; + case R.id.action_details: + String songFilePath = SongFileLoader.getSongFile(this, song.id); + File songFile = new File(songFilePath); + SongDetailDialogHelper.getDialog(this, songFile).show(); + return true; + case R.id.action_go_to_album: + goToAlbumDetailsActivity(song.albumId, null); + return true; + case R.id.action_go_to_artist: + goToArtistDetailsActivity(song.artistId, null); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + protected void onResume() { + super.onResume(); + startMusicControllerStateUpdateThread(); + } + + @Override + protected void onPause() { + super.onPause(); + killThreads = true; + } + + private void updateCurrentSong() { + getCurrentSongAndQueue(); + setHeadersText(); + setUpArtistArt(); + setUpAlbumArtAndApplyPalette(); + totalSongDuration.setText(MusicUtil.getReadableDurationString(song.duration)); + currentSongProgress.setText(MusicUtil.getReadableDurationString(-1)); + } + + private void moveSeekBarIntoPlace() { + RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) progressSlider.getLayoutParams(); + progressSlider.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + lp.setMargins(0, 0, 0, -(progressSlider.getMeasuredHeight() / 2)); + progressSlider.setLayoutParams(lp); + } + + private void setHeadersText() { + songTitle.setText(song.title); + songArtist.setText(song.artistName); + } + + private void setUpAlbumArtAndApplyPalette() { + ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(song.albumId).toString(), albumArt, new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) { + albumArt.setImageResource(R.drawable.default_album_art); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + albumArt.setImageResource(R.drawable.default_album_art); + setStandardColors(); + } + + @Override + public void onLoadingComplete(String imageUri, final View view, Bitmap loadedImage) { + applyPalette(loadedImage); + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + albumArt.setImageResource(R.drawable.default_album_art); + setStandardColors(); + } + }); + } + + private void setUpArtistArt() { + if (artistArt != null) { + artistArt.setImageResource(R.drawable.default_artist_image); + LastFMArtistImageLoader.loadArtistImage(this, song.artistName, new LastFMArtistImageLoader.ArtistImageLoaderCallback() { + @Override + public void onArtistImageLoaded(Bitmap artistImage) { + artistArt.setImageBitmap(artistImage); + } + }); + } + } + + private void getCurrentSongAndQueue() { + if (getApp().getMusicPlayerRemote().getPosition() >= 0) { + song = getApp().getMusicPlayerRemote().getPlayingQueue().get(getApp().getMusicPlayerRemote().getPosition()); + } else { + finish(); + } + } + + private void initViews() { + nextButton = (ImageButton) findViewById(R.id.next_button); + prevButton = (ImageButton) findViewById(R.id.prev_button); + repeatButton = (ImageButton) findViewById(R.id.repeat_button); + shuffleButton = (ImageButton) findViewById(R.id.shuffle_button); + albumArt = (ImageView) findViewById(R.id.album_art); + artistArt = (ImageView) findViewById(R.id.artist_image); + songTitle = (TextView) findViewById(R.id.song_title); + songArtist = (TextView) findViewById(R.id.song_artist); + currentSongProgress = (TextView) findViewById(R.id.song_current_progress); + totalSongDuration = (TextView) findViewById(R.id.song_total_time); + footer = findViewById(R.id.footer); + progressSlider = (SeekBar) findViewById(R.id.progress_slider); + } + + private void applyPalette(Bitmap bitmap) { + Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + Palette.Swatch swatch = palette.getVibrantSwatch(); + if (swatch != null) { + animateColorChange(swatch.getRgb()); + songTitle.setTextColor(swatch.getTitleTextColor()); + songArtist.setTextColor(swatch.getBodyTextColor()); + } else { + setStandardColors(); + } + } + }); + } + + private void setStandardColors() { + int songTitleTextColor = Util.resolveColor(this, R.attr.title_text_color); + int artistNameTextColor = Util.resolveColor(this, R.attr.caption_text_color); + int colorPrimary = Util.resolveColor(MusicControllerActivity.this, R.attr.colorPrimary); + + animateColorChange(colorPrimary); + + songTitle.setTextColor(songTitleTextColor); + songArtist.setTextColor(artistNameTextColor); + } + + private void animateColorChange(final int newColor) { + if (lastFooterColor != -1 && lastFooterColor != newColor) { + ViewUtil.animateViewColor(footer, lastFooterColor, newColor, 300); + } else { + footer.setBackgroundColor(newColor); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setNavigationBarColor(newColor); + } + lastFooterColor = newColor; + } + + private void setUpMusicControllers() { + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + getApp().getMusicPlayerRemote().seekTo(progress); + } + currentSongProgress.setText(MusicUtil.getReadableDurationString(progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + } + + private void setUpPrevNext() { + nextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getApp().getMusicPlayerRemote().playNextSong(); + } + }); + prevButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getApp().getMusicPlayerRemote().back(); + } + }); + } + + private void setUpShuffleButton() { + updateShuffleState(); + shuffleButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getApp().getMusicPlayerRemote().toggleShuffleMode(); + } + }); + } + + private void setUpRepeatButton() { + updateRepeatState(); + repeatButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getApp().getMusicPlayerRemote().cycleRepeatMode(); + } + }); + } + + private void updateRepeatState() { + switch (getApp().getMusicPlayerRemote().getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_grey600_48dp); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_48dp); + break; + default: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_48dp); + break; + } + } + + private void updateShuffleState() { + switch (getApp().getMusicPlayerRemote().getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setImageResource(R.drawable.ic_shuffle_white_48dp); + break; + default: + shuffleButton.setImageResource(R.drawable.ic_shuffle_grey600_48dp); + break; + } + } + + @Override + protected void updateControllerState() { + super.updateControllerState(); + updateRepeatState(); + updateShuffleState(); + } + + private void startMusicControllerStateUpdateThread() { + killThreads = false; + new Thread(new Runnable() { + @Override + public void run() { + int currentPosition = 0; + int total = 0; + while (getApp().getMusicPlayerRemote().isMusicBound() && !killThreads) { + try { + total = getApp().getMusicPlayerRemote().getSongDurationMillis(); + currentPosition = getApp().getMusicPlayerRemote().getSongProgressMillis(); + Thread.sleep(1); + } catch (InterruptedException e) { + return; + } catch (Exception e) { + } + progressSlider.setMax(total); + progressSlider.setProgress(currentPosition); + } + } + }).start(); + } + + @Override + public void onMusicRemoteEvent(MusicRemoteEvent event) { + super.onMusicRemoteEvent(event); + switch (event.getAction()) { + case MusicRemoteEvent.NEXT: + updateCurrentSong(); + break; + case MusicRemoteEvent.PREV: + updateCurrentSong(); + break; + case MusicRemoteEvent.REPEAT_MODE_CHANGED: + updateRepeatState(); + break; + case MusicRemoteEvent.SHUFFLE_MODE_CHANGED: + updateShuffleState(); + break; + } + } + + private void prepareViewsForOpenAnimation() { + footer.setPivotY(0); + footer.setScaleY(0); + } + + private void animateActivityOpened(int startDelay) { + ViewPropertyAnimator.animate(footer) + .scaleX(1) + .scaleY(1) + .setInterpolator(new DecelerateInterpolator(4)) + .setDuration(DEFAULT_ANIMATION_TIME) + .setStartDelay(startDelay) + .start(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + animateActivityOpened(DEFAULT_DELAY); + } + } + + private void setUpToolBar() { + setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + getSupportActionBar().setTitle(null); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + protected boolean openCurrentPlayingIfPossible(Pair[] sharedViews) { + onBackPressed(); + return true; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/base/AbsBaseActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/base/AbsBaseActivity.java new file mode 100644 index 00000000..7db540d0 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/base/AbsBaseActivity.java @@ -0,0 +1,128 @@ +package com.kabouzeid.materialmusic.ui.activities.base; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.util.Pair; +import android.support.v7.app.ActionBarActivity; +import android.widget.Toast; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.songadapter.SongAdapter; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.ui.activities.AlbumDetailActivity; +import com.kabouzeid.materialmusic.ui.activities.ArtistDetailActivity; +import com.kabouzeid.materialmusic.ui.activities.MusicControllerActivity; +import com.kabouzeid.materialmusic.util.Util; + +/** + * Created by karim on 20.01.15. + */ +public abstract class AbsBaseActivity extends ActionBarActivity implements KabViewsDisableAble, SongAdapter.GoToAble { + private App app; + private boolean areViewsEnabled; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(getApp().getAppTheme()); + super.onCreate(savedInstanceState); + } + + @Override + protected void onResume() { + super.onResume(); + enableViews(); + } + + protected void setUpTranslucence(boolean statusBarTranslucent, boolean navigationBarTranslucent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Util.setStatusBarTranslucent(getWindow(), statusBarTranslucent); + if (getApp().isInPortraitMode() || getApp().isTablet()) { + Util.setNavBarTranslucent(getWindow(), navigationBarTranslucent); + } + } + } + + protected boolean openCurrentPlayingIfPossible(Pair[] sharedViews) { + if (getApp().getMusicPlayerRemote().getPosition() != -1) { + if (areViewsEnabled()) { + disableViews(); + Intent intent = new Intent(this, MusicControllerActivity.class); + if (sharedViews != null) { + ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this, + sharedViews + ); + ActivityCompat.startActivity(this, intent, optionsCompat.toBundle()); + } else { + startActivity(intent); + } + return true; + } + } else { + Toast.makeText(this, getResources().getString(R.string.nothing_playing), Toast.LENGTH_SHORT).show(); + } + return false; + } + + @Override + public void goToAlbum(int albumId) { + goToAlbumDetailsActivity(albumId, null); + } + + @Override + public void goToArtist(int artistId) { + goToArtistDetailsActivity(artistId, null); + } + + public void goToAlbumDetailsActivity(int albumId, Pair[] sharedViews) { + final Intent intent = new Intent(this, AlbumDetailActivity.class); + intent.putExtra(AppKeys.E_ALBUM, albumId); + if (sharedViews != null) { + ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this, + sharedViews + ); + ActivityCompat.startActivity(this, intent, optionsCompat.toBundle()); + } else { + startActivity(intent); + } + } + + public void goToArtistDetailsActivity(int artistId, Pair[] sharedViews) { + final Intent intent = new Intent(this, ArtistDetailActivity.class); + intent.putExtra(AppKeys.E_ARTIST, artistId); + if (sharedViews != null) { + ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this, + sharedViews + ); + ActivityCompat.startActivity(this, intent, optionsCompat.toBundle()); + } else { + startActivity(intent); + } + } + + @Override + public boolean areViewsEnabled() { + return areViewsEnabled; + } + + @Override + public void enableViews() { + areViewsEnabled = true; + } + + @Override + public void disableViews() { + areViewsEnabled = false; + } + + protected App getApp() { + if (app == null) { + app = (App) getApplicationContext(); + } + return app; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/base/AbsFabActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/base/AbsFabActivity.java new file mode 100644 index 00000000..50ce89af --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/base/AbsFabActivity.java @@ -0,0 +1,163 @@ +package com.kabouzeid.materialmusic.ui.activities.base; + +import android.os.Bundle; +import android.support.v4.util.Pair; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Toast; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.interfaces.OnMusicRemoteEventListener; +import com.kabouzeid.materialmusic.misc.SmallOnGestureListener; +import com.kabouzeid.materialmusic.model.MusicRemoteEvent; +import com.melnykov.fab.FloatingActionButton; + +/** + * Created by karim on 22.01.15. + */ +public abstract class AbsFabActivity extends AbsBaseActivity implements OnMusicRemoteEventListener { + private FloatingActionButton fab; + + protected FloatingActionButton getFab() { + if (fab == null) { + fab = (FloatingActionButton) findViewById(R.id.fab); + } + return fab; + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + setUpFab(); + } + + @Override + protected void onResume() { + super.onResume(); + updateControllerState(); + getApp().getMusicPlayerRemote().addOnMusicRemoteEventListener(this); + } + + @Override + protected void onStop() { + super.onStop(); + getApp().getMusicPlayerRemote().removeOnMusicRemoteEventListener(this); + } + + @Override + public void enableViews() { + super.enableViews(); + fab.setEnabled(true); + } + + @Override + public void disableViews() { + super.disableViews(); + fab.setEnabled(false); + } + + @Override + protected boolean openCurrentPlayingIfPossible(Pair[] sharedViews) { + return super.openCurrentPlayingIfPossible(getSharedViewsWithFab(sharedViews)); + } + + @Override + public void goToArtistDetailsActivity(int artistId, Pair[] sharedViews) { + super.goToArtistDetailsActivity(artistId, getSharedViewsWithFab(sharedViews)); + } + + @Override + public void goToAlbumDetailsActivity(int albumId, Pair[] sharedViews) { + super.goToAlbumDetailsActivity(albumId, getSharedViewsWithFab(sharedViews)); + } + + private Pair[] getSharedViewsWithFab(Pair[] sharedViews) { + Pair[] sharedViewsWithFab; + if (sharedViews != null) { + sharedViewsWithFab = new Pair[sharedViews.length + 1]; + for (int i = 0; i < sharedViews.length; i++) { + sharedViewsWithFab[i] = sharedViews[i]; + } + } else { + sharedViewsWithFab = new Pair[1]; + } + sharedViewsWithFab[sharedViewsWithFab.length - 1] = Pair.create((View) getFab(), getString(R.string.transition_fab)); + return sharedViewsWithFab; + } + + private void setUpFab() { + updateFabState(); + final GestureDetector gestureDetector = new GestureDetector(this, new SmallOnGestureListener() { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + openCurrentPlayingIfPossible(null); + return true; + } + }); + + getFab().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (getApp().getMusicPlayerRemote().getPosition() != -1) { + if (getApp().getMusicPlayerRemote().isPlaying()) { + getApp().getMusicPlayerRemote().pauseSong(); + } else { + getApp().getMusicPlayerRemote().resumePlaying(); + } + } else { + Toast.makeText(AbsFabActivity.this, getResources().getString(R.string.nothing_playing), Toast.LENGTH_SHORT).show(); + } + } + }); + + getFab().setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + Toast.makeText(AbsFabActivity.this, getResources().getString(R.string.hint_fling_to_open), Toast.LENGTH_SHORT).show(); + return true; + } + }); + + getFab().setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + gestureDetector.onTouchEvent(event); + return false; + } + }); + } + + protected void updateControllerState() { + updateFabState(); + } + + private void updateFabState() { + if (getApp().getMusicPlayerRemote().isPlaying()) { + getFab().setImageResource(R.drawable.ic_pause_white_48dp); + } else { + getFab().setImageResource(R.drawable.ic_play_arrow_white_48dp); + } + } + + @Override + public void onMusicRemoteEvent(MusicRemoteEvent event) { + switch (event.getAction()) { + case MusicRemoteEvent.PLAY: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_pause_white_48dp)); + break; + case MusicRemoteEvent.PAUSE: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_play_arrow_white_48dp)); + break; + case MusicRemoteEvent.RESUME: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_pause_white_48dp)); + break; + case MusicRemoteEvent.STOP: + fab.setImageDrawable(getResources().getDrawable(R.drawable.ic_play_arrow_white_48dp)); + break; + case MusicRemoteEvent.QUEUE_COMPLETED: + fab.setImageResource(R.drawable.ic_play_arrow_white_48dp); + break; + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/AbsTagEditorActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/AbsTagEditorActivity.java new file mode 100644 index 00000000..ffff28cb --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/AbsTagEditorActivity.java @@ -0,0 +1,464 @@ +package com.kabouzeid.materialmusic.ui.activities.tageditor; + +import android.app.SearchManager; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.graphics.Palette; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.OvershootInterpolator; +import android.widget.ImageView; +import android.widget.TextView; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.misc.SmallObservableScrollViewCallbacks; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.kabouzeid.materialmusic.util.Util; +import com.kabouzeid.materialmusic.util.ViewUtil; +import com.melnykov.fab.FloatingActionButton; +import com.nineoldandroids.view.ViewHelper; +import com.nineoldandroids.view.ViewPropertyAnimator; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.utils.MemoryCacheUtils; + +import org.jaudiotagger.audio.AudioFile; +import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.audio.exceptions.CannotReadException; +import org.jaudiotagger.audio.exceptions.CannotWriteException; +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; +import org.jaudiotagger.tag.FieldKey; +import org.jaudiotagger.tag.Tag; +import org.jaudiotagger.tag.TagException; +import org.jaudiotagger.tag.images.Artwork; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Created by karim on 18.01.15. + */ +public abstract class AbsTagEditorActivity extends ActionBarActivity { + public static final String TAG = AbsTagEditorActivity.class.getSimpleName(); + private static final int REQUEST_CODE_SELECT_IMAGE = 1337; + + private App app; + private int id; + private int headerVariableSpace; + private int paletteColorPrimary; + private boolean isInNoImageMode; + + private FloatingActionButton fab; + private ObservableScrollView scrollView; + private Toolbar toolBar; + private ImageView image; + private View header; + + private List songPaths; + + @Override + protected void onCreate(Bundle savedInstanceState) { + app = (App) getApplicationContext(); + setTheme(app.getAppTheme()); + setUpTranslucence(); + + super.onCreate(savedInstanceState); + setContentView(getContentViewResId()); + + getIntentExtras(); + headerVariableSpace = getResources().getDimensionPixelSize(R.dimen.tagEditorHeaderVariableSpace); + songPaths = getSongPaths(); + + initViews(); + setUpViews(); + setUpToolBar(); + } + + private void initViews() { + fab = (FloatingActionButton) findViewById(R.id.fab); + scrollView = (ObservableScrollView) findViewById(R.id.observableScrollView); + toolBar = (Toolbar) findViewById(R.id.toolbar); + image = (ImageView) findViewById(R.id.image); + header = findViewById(R.id.header); + } + + private void setUpViews() { + restoreStandardColors(); + setUpScrollView(); + setUpFab(); + setUpImageView(); + } + + private void setUpScrollView() { + scrollView.setScrollViewCallbacks(observableScrollViewCallbacks); + scrollView.post(new Runnable() { + @Override + public void run() { + scrollView.scrollVerticallyTo(headerVariableSpace / 2); + } + }); + } + + private void setUpImageView() { + loadCurrentImage(); + image.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new MaterialDialog.Builder(AbsTagEditorActivity.this) + .title("Update image") + .items(new CharSequence[]{"Download from LastFM", "Pick from internal storage", "Web search", "Delete"}) + .itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { + switch (which) { + case 0: + getImageFromLastFM(); + break; + case 1: + startImagePicker(); + break; + case 2: + searchImageOnWeb(); + break; + case 3: + deleteImage(); + break; + } + } + }) + .build() + .show(); + } + }); + } + + protected void searchWebFor(List strings) { + StringBuilder stringBuilder = new StringBuilder(); + for (String string : strings) { + stringBuilder.append(string); + stringBuilder.append(" "); + } + Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.putExtra(SearchManager.QUERY, stringBuilder.toString()); + startActivity(intent); + } + + private void startImagePicker() { + Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); + photoPickerIntent.setType("image/*"); + startActivityForResult(photoPickerIntent, REQUEST_CODE_SELECT_IMAGE); + } + + private void setUpTranslucence() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Util.setStatusBarTranslucent(getWindow(), false); + Util.setNavBarTranslucent(getWindow(), false); + } + } + + private void getIntentExtras() { + Bundle intentExtras = getIntent().getExtras(); + if (intentExtras != null) { + id = intentExtras.getInt(AppKeys.E_ID); + } + } + + protected void setUpToolBar() { + setSupportActionBar(toolBar); + getSupportActionBar().setTitle(getResources().getString(R.string.tag_editor)); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_tag_editor, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + super.onBackPressed(); + return true; + case R.id.action_settings: + return true; + } + return super.onOptionsItemSelected(item); + } + + protected void setUpFab() { + ViewHelper.setScaleX(fab, 0); + ViewHelper.setScaleY(fab, 0); + fab.setEnabled(false); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + save(); + } + }); + } + + private void showFab() { + ViewPropertyAnimator.animate(fab) + .setDuration(500) + .setInterpolator(new OvershootInterpolator()) + .scaleX(1) + .scaleY(1) + .start(); + fab.setEnabled(true); + } + + private SmallObservableScrollViewCallbacks observableScrollViewCallbacks = new SmallObservableScrollViewCallbacks() { + @Override + public void onScrollChanged(int scrollY, boolean b, boolean b2) { + float alpha; + if (!isInNoImageMode) { + alpha = 1 - (float) Math.max(0, headerVariableSpace - scrollY) / headerVariableSpace; + } else { + ViewHelper.setTranslationY(header, scrollY); + alpha = 1; + } + ViewUtil.setBackgroundAlpha(toolBar, alpha, paletteColorPrimary); + ViewUtil.setBackgroundAlpha(header, alpha, paletteColorPrimary); + ViewHelper.setTranslationY(image, scrollY / 2); + } + }; + + private void applyPalette(final Bitmap bitmap) { + if (bitmap != null) { + Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + final int vibrantColor = palette.getVibrantColor(Util.resolveColor(AbsTagEditorActivity.this, R.attr.colorPrimary)); + paletteColorPrimary = vibrantColor; + observableScrollViewCallbacks.onScrollChanged(scrollView.getCurrentScrollY(), false, false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setStatusBarColor(vibrantColor); + getWindow().setNavigationBarColor(vibrantColor); + } + } + }); + } else { + restoreStandardColors(); + } + } + + private void restoreStandardColors() { + final int vibrantColor = Util.resolveColor(this, R.attr.colorPrimary); + paletteColorPrimary = vibrantColor; + observableScrollViewCallbacks.onScrollChanged(scrollView.getCurrentScrollY(), false, false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().setStatusBarColor(vibrantColor); + getWindow().setNavigationBarColor(vibrantColor); + } + } + + protected void setNoImageMode() { + isInNoImageMode = true; + image.setVisibility(View.GONE); + image.setEnabled(false); + scrollView.setPadding(0, Util.getActionBarSize(this), 0, 0); + observableScrollViewCallbacks.onScrollChanged(scrollView.getCurrentScrollY(), false, false); + } + + protected void dataChanged() { + showFab(); + } + + protected void setImageBitmap(final Bitmap bitmap) { + if (bitmap != null) { + image.setImageBitmap(bitmap); + applyPalette(bitmap); + } + } + + protected void setImageRes(int resId) { + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId); + setImageBitmap(bitmap); + } + + private void rescanMedia() { + String[] toBeScanned = new String[songPaths.size()]; + toBeScanned = songPaths.toArray(toBeScanned); + MediaScannerConnection.scanFile(this, toBeScanned, null, null); + } + + private AudioFile getAudioFile(String path) { + try { + return AudioFileIO.read(new File(path)); + } catch (CannotReadException | ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException e) { + Log.e(TAG, "error while trying to create the AudioFile from File", e); + } + return null; + } + + protected void writeValuesToFiles(final Map fieldKeyValueMap) { + writeValuesToFiles(fieldKeyValueMap, null, false); + } + + protected void writeValuesToFiles(final Map fieldKeyValueMap, final Artwork artwork) { + if (artwork == null) { + writeValuesToFiles(fieldKeyValueMap, null, true); + } else { + writeValuesToFiles(fieldKeyValueMap, artwork, false); + } + } + + protected void writeValuesToFiles(final Map fieldKeyValueMap, boolean deleteArtwork) { + writeValuesToFiles(fieldKeyValueMap, null, deleteArtwork); + } + + protected void writeValuesToFiles(final Map fieldKeyValueMap, final Artwork artwork, final boolean deleteArtwork) { + final String writingFileStr = getResources().getString(R.string.writing_file_number); + final MaterialDialog progressDialog = new MaterialDialog.Builder(AbsTagEditorActivity.this) + .customView(R.layout.dialog_loading, true) + .title(writingFileStr) + .cancelable(false) + .build(); + final TextView progressText = (TextView) progressDialog.getCustomView().findViewById(R.id.text); + progressDialog.show(); + new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < songPaths.size(); i++) { + String songPath = songPaths.get(i); + final int finalI = i; + runOnUiThread(new Runnable() { + @Override + public void run() { + progressText.setText((finalI + 1) + "/" + songPaths.size()); + } + }); + try { + AudioFile audioFile = AudioFileIO.read(new File(songPath)); + Tag tag = audioFile.getTagOrCreateAndSetDefault(); + for (Map.Entry entry : fieldKeyValueMap.entrySet()) { + tag.setField(entry.getKey(), entry.getValue()); + } + if (deleteArtwork) { + tag.deleteArtworkField(); + } else if (artwork != null) { + tag.deleteArtworkField(); + tag.setField(artwork); + } + audioFile.commit(); + } catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { + Log.e(TAG, "Error while reading audio file.", e); + } catch (CannotWriteException e) { + Log.e(TAG, "Error while writing audio file.", e); + } + } + if (deleteArtwork) { + String imagePath = MusicUtil.getAlbumArtUri(getId()).toString(); + ImageLoader.getInstance().getDiskCache().remove(imagePath); + MemoryCacheUtils.removeFromCache(imagePath, ImageLoader.getInstance().getMemoryCache()); + MusicUtil.deleteAlbumArt(AbsTagEditorActivity.this, getId()); + } else if (artwork != null) { + String imagePath = MusicUtil.getAlbumArtUri(getId()).toString(); + MemoryCacheUtils.removeFromCache(imagePath, ImageLoader.getInstance().getMemoryCache()); + ImageLoader.getInstance().getDiskCache().remove(imagePath); + } + progressDialog.dismiss(); + rescanMedia(); + restartApp(); + } + }).start(); + } + + private void restartApp() { + Intent i = getBaseContext().getPackageManager() + .getLaunchIntentForPackage(getBaseContext().getPackageName()); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) { + super.onActivityResult(requestCode, resultCode, imageReturnedIntent); + switch (requestCode) { + case REQUEST_CODE_SELECT_IMAGE: + if (resultCode == RESULT_OK) { + Uri selectedImage = imageReturnedIntent.getData(); + loadImageFromFile(selectedImage); + } + } + } + + protected abstract void save(); + + protected abstract int getContentViewResId(); + + protected abstract void loadCurrentImage(); + + protected abstract void getImageFromLastFM(); + + protected abstract void searchImageOnWeb(); + + protected abstract void loadImageFromFile(Uri selectedFile); + + protected abstract void deleteImage(); + + protected abstract List getSongPaths(); + + protected App getApp() { + return app; + } + + protected int getId() { + return id; + } + + protected String getSongTitle() throws NullPointerException { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.TITLE); + } + + protected String getAlbumTitle() throws NullPointerException { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ALBUM); + } + + protected String getArtistName() throws NullPointerException { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ARTIST); + } + + protected String getAlbumArtistName() throws NullPointerException { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ALBUM_ARTIST); + } + + protected String getGenreName() throws NullPointerException { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.GENRE); + } + + protected String getSongYear() throws NullPointerException { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.YEAR); + } + + protected String getTrackNumber() throws NullPointerException { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.TRACK); + } + + protected Bitmap getAlbumArt() throws NullPointerException { + Artwork artworkTag = getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirstArtwork(); + if (artworkTag != null) { + byte[] artworkBinaryData = artworkTag.getBinaryData(); + return BitmapFactory.decodeByteArray(artworkBinaryData, 0, artworkBinaryData.length); + } + return null; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/AlbumTagEditorActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/AlbumTagEditorActivity.java new file mode 100644 index 00000000..64db4390 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/AlbumTagEditorActivity.java @@ -0,0 +1,199 @@ +package com.kabouzeid.materialmusic.ui.activities.tageditor; + +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.widget.EditText; +import android.widget.Toast; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.lastfm.album.LastFMAlbumImageLoader; +import com.kabouzeid.materialmusic.loader.AlbumSongLoader; +import com.kabouzeid.materialmusic.loader.SongFileLoader; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.util.MusicUtil; +import com.kabouzeid.materialmusic.util.Util; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.process.BitmapProcessor; + +import org.jaudiotagger.tag.FieldKey; +import org.jaudiotagger.tag.images.Artwork; +import org.jaudiotagger.tag.images.ArtworkFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class AlbumTagEditorActivity extends AbsTagEditorActivity implements TextWatcher { + public static final String TAG = AlbumTagEditorActivity.class.getSimpleName(); + + private File albumArtFile; + private Bitmap albumArtBitmap; + private boolean deleteAlbumArt; + + private EditText albumTitle; + private EditText albumArtistName; + private EditText genreName; + private EditText year; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + initViews(); + setUpViews(); + } + + private void initViews() { + albumTitle = (EditText) findViewById(R.id.album_title); + albumArtistName = (EditText) findViewById(R.id.album_artist); + genreName = (EditText) findViewById(R.id.genre); + year = (EditText) findViewById(R.id.year); + } + + private void setUpViews() { + fillViewsWithFileTags(); + albumTitle.addTextChangedListener(this); + albumArtistName.addTextChangedListener(this); + genreName.addTextChangedListener(this); + year.addTextChangedListener(this); + } + + + private void fillViewsWithFileTags() { + albumTitle.setText(getAlbumTitle()); + albumArtistName.setText(getAlbumArtistName()); + genreName.setText(getGenreName()); + year.setText(getSongYear()); + } + + @Override + protected void save() { + Artwork artwork = null; + Map fieldKeyValueMap = new EnumMap<>(FieldKey.class); + fieldKeyValueMap.put(FieldKey.ALBUM, albumTitle.getText().toString()); + fieldKeyValueMap.put(FieldKey.ALBUM_ARTIST, albumArtistName.getText().toString()); + fieldKeyValueMap.put(FieldKey.GENRE, genreName.getText().toString()); + fieldKeyValueMap.put(FieldKey.YEAR, year.getText().toString()); + + try { + albumArtFile = MusicUtil.getAlbumArtFile(this, String.valueOf(getId())); + } catch (IOException e) { + Log.e(TAG, "error while creating albumArtFile", e); + } + + if (albumArtBitmap != null && albumArtFile != null) { + try { + albumArtBitmap.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); + artwork = ArtworkFactory.createArtworkFromFile(albumArtFile); + MusicUtil.insertAlbumArt(this, getId(), albumArtFile.getAbsolutePath()); + } catch (IOException e) { + Log.e(TAG, "error while trying to create the artwork from file", e); + } + } + writeValuesToFiles(fieldKeyValueMap, artwork, deleteAlbumArt); + } + + @Override + protected int getContentViewResId() { + return R.layout.activity_album_tag_editor; + } + + @Override + protected void loadCurrentImage() { + setImageBitmap(getAlbumArt()); + deleteAlbumArt = false; + } + + @Override + protected void getImageFromLastFM() { + String albumTitleStr = albumTitle.getText().toString(); + String albumArtistNameStr = albumArtistName.getText().toString(); + if (albumArtistNameStr.trim().equals("") || albumTitleStr.trim().equals("")) { + Toast.makeText(this, getResources().getString(R.string.album_or_artist_empty), Toast.LENGTH_SHORT).show(); + return; + } + LastFMAlbumImageLoader.loadAlbumImage(this, albumTitleStr, albumArtistNameStr, new LastFMAlbumImageLoader.AlbumImageLoaderCallback() { + @Override + public void onAlbumImageLoaded(Bitmap albumImage, String uri) { + if (albumImage != null) { + setImageBitmap(albumImage); + albumArtBitmap = albumImage; + deleteAlbumArt = false; + dataChanged(); + Toast.makeText(AlbumTagEditorActivity.this, "Success.", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(AlbumTagEditorActivity.this, "Failed.", Toast.LENGTH_SHORT).show(); + } + } + }); + } + + @Override + protected void searchImageOnWeb() { + List query = new ArrayList<>(); + query.add(albumTitle.getText().toString()); + query.add(albumArtistName.getText().toString()); + searchWebFor(query); + } + + @Override + protected void loadImageFromFile(final Uri selectedFileUri) { + DisplayImageOptions options = new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(false) + .postProcessor(new BitmapProcessor() { + @Override + public Bitmap process(Bitmap bmp) { + return Util.getAlbumArtScaledBitmap(bmp, true); + } + }) + .build(); + albumArtBitmap = ImageLoader.getInstance().loadImageSync(selectedFileUri.toString(), options); + if (albumArtBitmap != null) { + setImageBitmap(albumArtBitmap); + deleteAlbumArt = false; + dataChanged(); + } + } + + @Override + protected void deleteImage() { + setImageRes(R.drawable.default_album_art); + deleteAlbumArt = true; + dataChanged(); + } + + @Override + protected List getSongPaths() { + List songs = AlbumSongLoader.getAlbumSongList(this, getId()); + List songIds = new ArrayList<>(); + for (Song song : songs) { + songIds.add(song.id); + } + return SongFileLoader.getSongFiles(this, songIds); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + dataChanged(); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/SongTagEditorActivity.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/SongTagEditorActivity.java new file mode 100644 index 00000000..8f3688ea --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/activities/tageditor/SongTagEditorActivity.java @@ -0,0 +1,130 @@ +package com.kabouzeid.materialmusic.ui.activities.tageditor; + +import android.net.Uri; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.widget.EditText; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.loader.SongFileLoader; + +import org.jaudiotagger.tag.FieldKey; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class SongTagEditorActivity extends AbsTagEditorActivity implements TextWatcher { + public static final String TAG = SongTagEditorActivity.class.getSimpleName(); + + private EditText songTitle; + private EditText albumTitle; + private EditText artistName; + private EditText genreName; + private EditText year; + private EditText trackNumber; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setNoImageMode(); + initViews(); + setUpViews(); + } + + private void initViews() { + songTitle = (EditText) findViewById(R.id.title1); + albumTitle = (EditText) findViewById(R.id.title2); + artistName = (EditText) findViewById(R.id.artist); + genreName = (EditText) findViewById(R.id.genre); + year = (EditText) findViewById(R.id.year); + trackNumber = (EditText) findViewById(R.id.track_number); + } + + private void setUpViews() { + fillViewsWithFileTags(); + songTitle.addTextChangedListener(this); + albumTitle.addTextChangedListener(this); + artistName.addTextChangedListener(this); + genreName.addTextChangedListener(this); + year.addTextChangedListener(this); + trackNumber.addTextChangedListener(this); + } + + + private void fillViewsWithFileTags() { + songTitle.setText(getSongTitle()); + albumTitle.setText(getAlbumTitle()); + artistName.setText(getArtistName()); + genreName.setText(getGenreName()); + year.setText(getSongYear()); + trackNumber.setText(getTrackNumber()); + } + + @Override + protected void save() { + Map fieldKeyValueMap = new EnumMap<>(FieldKey.class); + fieldKeyValueMap.put(FieldKey.TITLE, songTitle.getText().toString()); + fieldKeyValueMap.put(FieldKey.ALBUM, albumTitle.getText().toString()); + fieldKeyValueMap.put(FieldKey.ARTIST, artistName.getText().toString()); + fieldKeyValueMap.put(FieldKey.GENRE, genreName.getText().toString()); + fieldKeyValueMap.put(FieldKey.YEAR, year.getText().toString()); + fieldKeyValueMap.put(FieldKey.TRACK, trackNumber.getText().toString()); + writeValuesToFiles(fieldKeyValueMap); + } + + @Override + protected int getContentViewResId() { + return R.layout.activity_song_tag_editor; + } + + @Override + protected void loadCurrentImage() { + + } + + @Override + protected void getImageFromLastFM() { + + } + + @Override + protected void searchImageOnWeb() { + + } + + @Override + protected void loadImageFromFile(Uri imageFilePath) { + + } + + @Override + protected void deleteImage() { + + } + + @Override + protected List getSongPaths() { + List tempIdList = new ArrayList<>(); + tempIdList.add(getId()); + return SongFileLoader.getSongFiles(this, tempIdList); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + dataChanged(); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/NavigationDrawerFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/NavigationDrawerFragment.java new file mode 100644 index 00000000..a5e82e2c --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/NavigationDrawerFragment.java @@ -0,0 +1,205 @@ +package com.kabouzeid.materialmusic.ui.fragments; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.NavigationDrawerItemAdapter; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.NavigationDrawerItem; +import com.nhaarman.listviewanimations.appearance.simple.AlphaInAnimationAdapter; + +import java.util.ArrayList; + +public class NavigationDrawerFragment extends Fragment { + private static final String TAG = NavigationDrawerFragment.class.getSimpleName(); + + public static final int NAVIGATION_DRAWER_HEADER = -1; + private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position"; + + private App app; + + private NavigationDrawerCallbacks mCallbacks; + + private NavigationDrawerItemAdapter drawerAdapter; + + private DrawerLayout drawerLayout; + public View fragmentRootView; + private ListView drawerListView; + private View fragmentContainerView; + + private Button headerButton; + private ImageView albumArt; + private TextView songTitle; + private TextView songArtist; + + private int currentSelectedPosition; + private boolean fromSavedInstanceState; + private boolean userLearnedDrawer; + + public NavigationDrawerFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + app = (App) getActivity().getApplicationContext(); + userLearnedDrawer = app.getDefaultSharedPreferences().getBoolean(AppKeys.SP_USER_LEARNED_DRAWER, false); + + if (savedInstanceState != null) { + currentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION); + fromSavedInstanceState = true; + } else { + currentSelectedPosition = app.getDefaultSharedPreferences().getInt(AppKeys.SP_NAVIGATION_DRAWER_ITEM_POSITION, 0); + } + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_navigation_drawer, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + fragmentRootView = view; + + super.onViewCreated(view, savedInstanceState); + + initViews(); + setUpViews(); + + selectItem(currentSelectedPosition); + } + + private void initViews() { + drawerListView = (ListView) fragmentRootView.findViewById(R.id.navigation_drawer_list); + final View drawerHeader = fragmentRootView.findViewById(R.id.header); + headerButton = (Button) drawerHeader.findViewById(R.id.header_clickable); + albumArt = (ImageView) drawerHeader.findViewById(R.id.album_art); + songTitle = (TextView) drawerHeader.findViewById(R.id.song_title); + songArtist = (TextView) drawerHeader.findViewById(R.id.song_artist); + } + + private void setUpViews() { + headerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selectItem(NAVIGATION_DRAWER_HEADER); + } + }); + setUpListView(); + } + + private void setUpListView() { + final ArrayList navigationDrawerItems = new ArrayList<>(); + navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.all_songs), R.drawable.songs)); + navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.albums), R.drawable.album)); + navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.artists), R.drawable.interpret)); + navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.genres), R.drawable.songs)); + navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.playlists), R.drawable.playlist)); + + drawerAdapter = new NavigationDrawerItemAdapter(getActivity(), R.id.navigation_drawer, navigationDrawerItems); + + final AlphaInAnimationAdapter animationAdapter = new AlphaInAnimationAdapter(drawerAdapter); + animationAdapter.setAbsListView(drawerListView); + + drawerListView.setAdapter(animationAdapter); + drawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + selectItem(position); + } + }); + } + + public boolean isDrawerOpen() { + return drawerLayout != null && drawerLayout.isDrawerOpen(fragmentContainerView); + } + + public void setUp(int fragmentId, final DrawerLayout drawerLayout) { + fragmentContainerView = getActivity().findViewById(fragmentId); + this.drawerLayout = drawerLayout; + this.drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + + + if (!userLearnedDrawer && !fromSavedInstanceState) { + this.drawerLayout.openDrawer(fragmentContainerView); + userLearnedDrawer = true; + app.getDefaultSharedPreferences().edit().putBoolean(AppKeys.SP_USER_LEARNED_DRAWER, true).apply(); + } + } + + private void selectItem(int position) { + if (position != NAVIGATION_DRAWER_HEADER) { + currentSelectedPosition = position; + if (drawerAdapter != null) { + drawerAdapter.setChecked(position); + } + if (drawerLayout != null) { + //close drawer lag workaround + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + drawerLayout.closeDrawer(fragmentContainerView); + } + }, 100); + } + app.getDefaultSharedPreferences().edit().putInt(AppKeys.SP_NAVIGATION_DRAWER_ITEM_POSITION, position).apply(); + } + if (mCallbacks != null) { + mCallbacks.onNavigationDrawerItemSelected(position); + } + } + + public TextView getSongArtist() { + return songArtist; + } + + public ImageView getAlbumArtImageView() { + return albumArt; + } + + public TextView getSongTitle() { + return songTitle; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mCallbacks = (NavigationDrawerCallbacks) activity; + } catch (ClassCastException e) { + throw new ClassCastException("Activity must implement NavigationDrawerCallbacks."); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mCallbacks = null; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_SELECTED_POSITION, currentSelectedPosition); + } + + public static interface NavigationDrawerCallbacks { + void onNavigationDrawerItemSelected(int position); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/AbsViewPagerTabArtistListFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/AbsViewPagerTabArtistListFragment.java new file mode 100644 index 00000000..f3d409f0 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/AbsViewPagerTabArtistListFragment.java @@ -0,0 +1,151 @@ +package com.kabouzeid.materialmusic.ui.fragments.artistviewpager; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListAdapter; + +import com.github.ksoichiro.android.observablescrollview.ObservableGridView; +import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks; +import com.github.ksoichiro.android.observablescrollview.ScrollState; +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.ui.activities.ArtistDetailActivity; +import com.kabouzeid.materialmusic.util.Util; + +public abstract class AbsViewPagerTabArtistListFragment extends Fragment implements ObservableScrollViewCallbacks, KabViewsDisableAble { + public static final String TAG = AbsViewPagerTabArtistListFragment.class.getSimpleName(); + protected App app; + private ObservableGridView observableGridView; + private Activity parentActivity; + private int artistId = -1; + private String artistName = ""; + private int paddingViewHeight; + private boolean areViewsEnabled; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + app = (App) getActivity().getApplicationContext(); + parentActivity = getActivity(); + getArgs(); + + View view = inflater.inflate(R.layout.fragment_gridview, container, false); + observableGridView = (ObservableGridView) view.findViewById(R.id.scroll); + setGridViewPadding(); + observableGridView.setScrollViewCallbacks(this); + ListAdapter adapter = getAdapter(); + if (adapter != null) { + observableGridView.setAdapter(adapter); + } + + return view; + } + + private void setGridViewPadding() { + final int artistImageViewHeight = getResources().getDimensionPixelSize(R.dimen.header_image_height); + final int titleViewHeight = getResources().getDimensionPixelSize(R.dimen.title_view_height); + final int tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height); + + paddingViewHeight = artistImageViewHeight + titleViewHeight + tabHeight; + + if (app.isInPortraitMode() || app.isTablet()) { + observableGridView.setPadding(0, paddingViewHeight, 0, Util.getNavigationBarHeight(getActivity())); + } else { + observableGridView.setPadding(0, paddingViewHeight, 0, 0); + } + } + + private void getArgs() { + Bundle args = getArguments(); + if (args != null) { + artistId = args.getInt(ArtistDetailActivity.ARG_ARTIST_ID, -1); + artistName = args.getString(ArtistDetailActivity.ARG_ARTIST_NAME, ""); + } + } + + public int getY() { + return observableGridView.getCurrentScrollY() + paddingViewHeight; + } + + protected int getArtistId() { + return artistId; + } + + protected String getArtistName() { + return artistName; + } + + public Activity getParentActivity() { + return parentActivity; + } + + protected void setAdapter(ListAdapter adapter) { + observableGridView.setAdapter(adapter); + } + + protected void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener) { + observableGridView.setOnItemClickListener(onItemClickListener); + } + + protected void setColumns(int columns) { + observableGridView.setNumColumns(columns); + } + + /* + * + * IMPORTANT: + * + * You CAN return null here and use setAdapter(ListAdapter adapter) inside getAdapter() to manually set the adapter. + * + * (i.e. if you must set the adapter async). + * + * */ + protected abstract ListAdapter getAdapter(); + + @Override + public void onScrollChanged(int scrollY, boolean b, boolean b2) { + if (parentActivity instanceof ObservableScrollViewCallbacks) { + if (getUserVisibleHint()) { + ((ObservableScrollViewCallbacks) parentActivity).onScrollChanged(scrollY + paddingViewHeight, b, b2); + } + } + } + + @Override + public void onDownMotionEvent() { + + } + + @Override + public void onUpOrCancelMotionEvent(ScrollState scrollState) { + + } + + @Override + public void disableViews() { + areViewsEnabled = false; + observableGridView.setEnabled(false); + } + + @Override + public boolean areViewsEnabled() { + return areViewsEnabled; + } + + @Override + public void enableViews() { + areViewsEnabled = true; + observableGridView.setEnabled(true); + } + + @Override + public void onResume() { + super.onResume(); + enableViews(); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistAlbumFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistAlbumFragment.java new file mode 100644 index 00000000..f6e7a810 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistAlbumFragment.java @@ -0,0 +1,86 @@ +package com.kabouzeid.materialmusic.ui.fragments.artistviewpager; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.util.Pair; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.AlbumViewGridAdapter; +import com.kabouzeid.materialmusic.comparator.AlbumAlphabeticComparator; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.loader.ArtistAlbumLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.Album; +import com.kabouzeid.materialmusic.ui.activities.AlbumDetailActivity; +import com.melnykov.fab.FloatingActionButton; + +import java.util.Collections; +import java.util.List; + +/** + * Created by karim on 04.01.15. + */ +public class ViewPagerTabArtistAlbumFragment extends AbsViewPagerTabArtistListFragment { + private FloatingActionButton fab; + + @Override + protected ListAdapter getAdapter() { + List albums = ArtistAlbumLoader.getArtistAlbumList(getParentActivity(), getArtistId()); + Collections.sort(albums, new AlbumAlphabeticComparator()); + ListAdapter adapter = new AlbumViewGridAdapter(getParentActivity(), albums); + setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Album album = (Album) parent.getItemAtPosition(position); + View albumArtView = view.findViewById(R.id.album_art); + + openAlbumDetailsActivityIfPossible(album, albumArtView); + } + }); + setColumns(2); + return adapter; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + fab = (FloatingActionButton) getParentActivity().findViewById(R.id.fab); + } + + @SuppressWarnings("unchecked") + private void openAlbumDetailsActivityIfPossible(Album album, View albumArtForTransition) { + if (areParentActivitiesViewsEnabled()) { + disableViews(); + disableParentActivitiesViews(); + + final Intent intent = new Intent(getActivity(), AlbumDetailActivity.class); + intent.putExtra(AppKeys.E_ALBUM, album.id); + + final ActivityOptionsCompat activityOptions; + if (fab != null && albumArtForTransition != null) { + activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), + Pair.create(albumArtForTransition, getString(R.string.transition_album_cover)), + Pair.create((View) fab, getString(R.string.transition_fab)) + ); + } else { + activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity()); + } + ActivityCompat.startActivity(getActivity(), intent, activityOptions.toBundle()); + } + } + + private void disableParentActivitiesViews() { + if (getParentActivity() instanceof KabViewsDisableAble) { + ((KabViewsDisableAble) getParentActivity()).disableViews(); + } + } + + private boolean areParentActivitiesViewsEnabled() { + return !(getParentActivity() instanceof KabViewsDisableAble) || ((KabViewsDisableAble) getParentActivity()).areViewsEnabled(); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistBioFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistBioFragment.java new file mode 100644 index 00000000..95f9cb25 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistBioFragment.java @@ -0,0 +1,71 @@ +package com.kabouzeid.materialmusic.ui.fragments.artistviewpager; + + +import android.content.Context; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListAdapter; +import android.widget.TextView; + +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.lastfm.artist.LastFMArtistBiographyLoader; + +import java.util.ArrayList; +import java.util.List; + +public class ViewPagerTabArtistBioFragment extends AbsViewPagerTabArtistListFragment { + + + @Override + protected ListAdapter getAdapter() { + final List strings = new ArrayList<>(); + strings.add("loading"); + ListAdapter adapter = new SimpleTextAdapter(getParentActivity(), strings); + setAdapter(adapter); + + LastFMArtistBiographyLoader.loadArtistBio(getParentActivity(), getArtistName(), new LastFMArtistBiographyLoader.ArtistBioLoaderCallback() { + @Override + public void onArtistBioLoaded(String biography) { + if (biography == null || biography.trim().equals("")) { + try { + biography = getResources().getString(R.string.biography_unavailable); + } catch (IllegalStateException e) { + Log.e(TAG, "error while trying to get ressources", e); + biography = "Errorm"; + } + } + strings.clear(); + strings.add(biography); + ListAdapter adapter = new SimpleTextAdapter(getParentActivity(), strings); + setAdapter(adapter); + } + }); + return null; + } + + private static class SimpleTextAdapter extends ArrayAdapter { + private Context context; + + public SimpleTextAdapter(Context context, List objects) { + super(context, R.layout.item_artist_details_biography, objects); + this.context = context; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + String string = getItem(position); + if (convertView == null) { + convertView = LayoutInflater.from(context).inflate(R.layout.item_artist_details_biography, parent, false); + } + TextView text = (TextView) convertView.findViewById(R.id.text); + text.setText(Html.fromHtml(string)); + text.setMovementMethod(LinkMovementMethod.getInstance()); + return convertView; + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistSongListFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistSongListFragment.java new file mode 100644 index 00000000..ab732b32 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/artistviewpager/ViewPagerTabArtistSongListFragment.java @@ -0,0 +1,38 @@ +package com.kabouzeid.materialmusic.ui.fragments.artistviewpager; + +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; + +import com.kabouzeid.materialmusic.adapter.songadapter.SongAdapter; +import com.kabouzeid.materialmusic.comparator.SongAlphabeticComparator; +import com.kabouzeid.materialmusic.loader.ArtistSongLoader; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.ui.activities.base.AbsBaseActivity; + +import java.util.Collections; +import java.util.List; + +/** + * Created by karim on 04.01.15. + */ +public class ViewPagerTabArtistSongListFragment extends AbsViewPagerTabArtistListFragment { + @Override + protected ListAdapter getAdapter() { + final List songs = ArtistSongLoader.getArtistSongList(getParentActivity(), getArtistId()); + Collections.sort(songs, new SongAlphabeticComparator()); + AbsBaseActivity absBaseActivity = null; + if (getParentActivity() instanceof AbsBaseActivity) { + absBaseActivity = (AbsBaseActivity) getParentActivity(); + } + ListAdapter adapter = new SongAdapter(getParentActivity(), absBaseActivity, songs); + setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + app.getMusicPlayerRemote().setPlayingQueue(songs); + app.getMusicPlayerRemote().playSongAt(position); + } + }); + return adapter; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/AlbumViewFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/AlbumViewFragment.java new file mode 100644 index 00000000..566c5a66 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/AlbumViewFragment.java @@ -0,0 +1,173 @@ +package com.kabouzeid.materialmusic.ui.fragments.mainactivityfragments; + +import android.app.Fragment; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.AlbumViewGridAdapter; +import com.kabouzeid.materialmusic.comparator.AlbumAlphabeticComparator; +import com.kabouzeid.materialmusic.interfaces.KabSearchAbleFragment; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.loader.AlbumLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.Album; +import com.kabouzeid.materialmusic.ui.activities.AlbumDetailActivity; +import com.kabouzeid.materialmusic.util.Util; +import com.melnykov.fab.FloatingActionButton; + +import java.util.Collections; +import java.util.List; + +/** + * Created by karim on 22.11.14. + */ +public class AlbumViewFragment extends Fragment implements KabViewsDisableAble, KabSearchAbleFragment { + public static final String TAG = AlbumViewFragment.class.getSimpleName(); + + private App app; + private AbsListView absListView; + private View fragmentRootView; + private FloatingActionButton fab; + private boolean areViewsEnabled; + + @Override + public void onCreate(Bundle savedInstanceState) { + app = (App) getActivity().getApplicationContext(); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_albumview, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + fragmentRootView = view; + + super.onViewCreated(view, savedInstanceState); + + initViews(); + setUpViews(); + } + + @Override + public void onResume() { + super.onResume(); + enableViews(); + } + + private void initViews() { + absListView = (AbsListView) fragmentRootView.findViewById(R.id.absList); + fab = (FloatingActionButton) getActivity().findViewById(R.id.fab); + } + + private void setUpViews() { + setUpAbsListView(); + } + + private void setUpAbsListView() { + List albums = AlbumLoader.getAllAlbums(getActivity()); + fillAbsListView(albums); + } + + private void setUpAbsListView(String query) { + List albums = AlbumLoader.getAlbums(getActivity(), query); + fillAbsListView(albums); + } + + private void fillAbsListView(List albums) { + Collections.sort(albums, new AlbumAlphabeticComparator()); + AlbumViewGridAdapter albumTileAdapter = new AlbumViewGridAdapter(getActivity(), albums); + absListView.setAdapter(albumTileAdapter); + absListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Album album = (Album) parent.getItemAtPosition(position); + View albumArtView = view.findViewById(R.id.album_art); + + openAlbumDetailsActivityIfPossible(album, albumArtView); + } + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (app.isInPortraitMode() || app.isTablet()) { + absListView.setPadding(0, Util.getActionBarSize(getActivity()) + Util.getStatusBarHeight(getActivity()), 0, Util.getNavigationBarHeight(getActivity())); + } else { + absListView.setPadding(0, Util.getActionBarSize(getActivity()) + Util.getStatusBarHeight(getActivity()), 0, 0); + } + } else { + absListView.setPadding(0, Util.getActionBarSize(getActivity()), 0, 0); + } + } + + @SuppressWarnings("unchecked") + private void openAlbumDetailsActivityIfPossible(Album album, View albumArtForTransition) { + if (areParentActivitiesViewsEnabled()) { + disableViews(); + disableParentActivitiesViews(); + + final Intent intent = new Intent(getActivity(), AlbumDetailActivity.class); + intent.putExtra(AppKeys.E_ALBUM, album.id); + + final ActivityOptionsCompat activityOptions; + if (fab != null && albumArtForTransition != null) { + activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), + Pair.create(albumArtForTransition, getString(R.string.transition_album_cover)), + Pair.create((View) fab, getString(R.string.transition_fab)) + ); + } else { + activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity()); + } + ActivityCompat.startActivity(getActivity(), intent, activityOptions.toBundle()); + } + } + + private void disableParentActivitiesViews() { + if (getActivity() instanceof KabViewsDisableAble) { + ((KabViewsDisableAble) getActivity()).disableViews(); + } + } + + private boolean areParentActivitiesViewsEnabled() { + return !(getActivity() instanceof KabViewsDisableAble) || ((KabViewsDisableAble) getActivity()).areViewsEnabled(); + } + + @Override + public void disableViews() { + areViewsEnabled = false; + absListView.setEnabled(false); + } + + @Override + public boolean areViewsEnabled() { + return areViewsEnabled; + } + + @Override + public void enableViews() { + areViewsEnabled = true; + absListView.setEnabled(true); + } + + @Override + public void search(String query) { + setUpAbsListView(query); + } + + @Override + public void returnToNonSearch() { + setUpAbsListView(); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/ArtistViewFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/ArtistViewFragment.java new file mode 100644 index 00000000..a0224008 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/ArtistViewFragment.java @@ -0,0 +1,155 @@ +package com.kabouzeid.materialmusic.ui.fragments.mainactivityfragments; + + +import android.app.Fragment; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.ArtistViewListAdapter; +import com.kabouzeid.materialmusic.comparator.ArtistAlphabeticComparator; +import com.kabouzeid.materialmusic.interfaces.KabSearchAbleFragment; +import com.kabouzeid.materialmusic.interfaces.KabViewsDisableAble; +import com.kabouzeid.materialmusic.loader.ArtistLoader; +import com.kabouzeid.materialmusic.misc.AppKeys; +import com.kabouzeid.materialmusic.model.Artist; +import com.kabouzeid.materialmusic.ui.activities.ArtistDetailActivity; +import com.kabouzeid.materialmusic.ui.activities.base.AbsFabActivity; +import com.kabouzeid.materialmusic.util.Util; + +import java.util.Collections; +import java.util.List; + +/** + * A simple {@link Fragment} subclass. + */ +public class ArtistViewFragment extends Fragment implements KabSearchAbleFragment, KabViewsDisableAble { + public static final String TAG = ArtistViewFragment.class.getSimpleName(); + + private App app; + private AbsListView absListView; + private View fragmentRootView; + private boolean areViewsEnabled; + + @Override + public void onCreate(Bundle savedInstanceState) { + app = (App) getActivity().getApplicationContext(); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_artist_view, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + fragmentRootView = view; + super.onViewCreated(view, savedInstanceState); + initViews(); + setUpViews(); + } + + @Override + public void onResume() { + super.onResume(); + enableViews(); + } + + private void initViews() { + absListView = (AbsListView) fragmentRootView.findViewById(R.id.absList); + } + + private void setUpViews() { + setUpAbsListView(); + } + + private void setUpAbsListView() { + List artists = ArtistLoader.getAllArtists(getActivity()); + fillAbsListView(artists); + } + + private void setUpAbsListView(String query) { + List artists = ArtistLoader.getArtists(getActivity(), query); + fillAbsListView(artists); + } + + private void fillAbsListView(List artists) { + Collections.sort(artists, new ArtistAlphabeticComparator()); + ArtistViewListAdapter artistAdapter = new ArtistViewListAdapter(getActivity(), artists); + absListView.setAdapter(artistAdapter); + absListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + final Artist artist = (Artist) parent.getItemAtPosition(position); + final View artistImageView = view.findViewById(R.id.artist_image); + + if (getActivity() instanceof AbsFabActivity) { + AbsFabActivity absFabActivity = (AbsFabActivity) getActivity(); + Pair[] sharedElements = {Pair.create(artistImageView, getString(R.string.transition_artist_image))}; + absFabActivity.goToArtistDetailsActivity(artist.id, sharedElements); + } else { + Intent intent = new Intent(getActivity(), ArtistDetailActivity.class); + intent.putExtra(AppKeys.E_ARTIST, artist.id); + startActivity(intent); + } + } + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (app.isInPortraitMode() || app.isTablet()) { + absListView.setPadding(0, Util.getActionBarSize(getActivity()) + Util.getStatusBarHeight(getActivity()), 0, Util.getNavigationBarHeight(getActivity())); + } else { + absListView.setPadding(0, Util.getActionBarSize(getActivity()) + Util.getStatusBarHeight(getActivity()), 0, 0); + } + } else { + absListView.setPadding(0, Util.getActionBarSize(getActivity()), 0, 0); + } + } + + @Override + public void search(String query) { + setUpAbsListView(query); + } + + @Override + public void returnToNonSearch() { + setUpAbsListView(); + } + + private void disableParentActivityViews() { + if (getActivity() instanceof KabViewsDisableAble) { + ((KabViewsDisableAble) getActivity()).disableViews(); + } + } + + private boolean areParentActivityViewsEnabled() { + return !(getActivity() instanceof KabViewsDisableAble) || ((KabViewsDisableAble) getActivity()).areViewsEnabled(); + } + + @Override + public void enableViews() { + areViewsEnabled = true; + absListView.setEnabled(true); + } + + @Override + public void disableViews() { + areViewsEnabled = false; + absListView.setEnabled(false); + } + + @Override + public boolean areViewsEnabled() { + return areViewsEnabled; + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/SongViewFragment.java b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/SongViewFragment.java new file mode 100644 index 00000000..3d48ccd5 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/ui/fragments/mainactivityfragments/SongViewFragment.java @@ -0,0 +1,110 @@ +package com.kabouzeid.materialmusic.ui.fragments.mainactivityfragments; + + +import android.app.Fragment; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.adapter.songadapter.SongViewListAdapter; +import com.kabouzeid.materialmusic.comparator.SongAlphabeticComparator; +import com.kabouzeid.materialmusic.interfaces.KabSearchAbleFragment; +import com.kabouzeid.materialmusic.loader.SongLoader; +import com.kabouzeid.materialmusic.model.Song; +import com.kabouzeid.materialmusic.ui.activities.base.AbsBaseActivity; +import com.kabouzeid.materialmusic.util.Util; + +import java.util.Collections; +import java.util.List; + +/** + * Created by karim on 29.12.14. + */ +public class SongViewFragment extends Fragment implements KabSearchAbleFragment { + public static final String TAG = SongViewFragment.class.getSimpleName(); + + private App app; + private AbsListView absListView; + private View fragmentRootView; + + @Override + public void onCreate(Bundle savedInstanceState) { + app = (App) getActivity().getApplicationContext(); + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_songview, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + fragmentRootView = view; + super.onViewCreated(view, savedInstanceState); + initViews(); + setUpViews(); + } + + private void initViews() { + absListView = (AbsListView) fragmentRootView.findViewById(R.id.absList); + } + + private void setUpViews() { + setUpAbsListView(); + } + + private void setUpAbsListView() { + List songs = SongLoader.getAllSongs(getActivity()); + fillAbsListView(songs); + } + + private void setUpAbsListView(String query) { + List songs = SongLoader.getSongs(getActivity(), query); + fillAbsListView(songs); + } + + private void fillAbsListView(final List songs) { + Collections.sort(songs, new SongAlphabeticComparator()); + AbsBaseActivity absBaseActivity = null; + if (getActivity() instanceof AbsBaseActivity) { + absBaseActivity = (AbsBaseActivity) getActivity(); + } + SongViewListAdapter songAdapter = new SongViewListAdapter(getActivity(), absBaseActivity, songs); + absListView.setAdapter(songAdapter); + absListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + app.getMusicPlayerRemote().setPlayingQueue(songs); + app.getMusicPlayerRemote().playSongAt(position); + } + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (app.isInPortraitMode() || app.isTablet()) { + absListView.setPadding(0, Util.getActionBarSize(getActivity()) + Util.getStatusBarHeight(getActivity()), 0, Util.getNavigationBarHeight(getActivity())); + } else { + absListView.setPadding(0, Util.getActionBarSize(getActivity()) + Util.getStatusBarHeight(getActivity()), 0, 0); + } + } else { + absListView.setPadding(0, Util.getActionBarSize(getActivity()), 0, 0); + } + } + + @Override + public void search(String query) { + setUpAbsListView(query); + } + + @Override + public void returnToNonSearch() { + setUpAbsListView(); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/util/ImageLoaderUtil.java b/app/src/main/java/com/kabouzeid/materialmusic/util/ImageLoaderUtil.java new file mode 100644 index 00000000..1d45c437 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/util/ImageLoaderUtil.java @@ -0,0 +1,92 @@ +package com.kabouzeid.materialmusic.util; + +import android.content.Context; +import android.graphics.Bitmap; +import android.view.View; +import android.widget.ImageView; + +import com.kabouzeid.materialmusic.R; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; +import com.nostra13.universalimageloader.utils.L; + +/** + * Created by karim on 28.12.14. + */ +public class ImageLoaderUtil { + public static void initImageLoader(Context context) { + DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(false) + .build(); + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) + .defaultDisplayImageOptions(defaultOptions) + //.memoryCache(new LRULimitedMemoryCache(1024*1024*CACHE_SIZE_MB)) + .build(); + ImageLoader.getInstance().init(config); + + L.writeLogs(false); + } + + public static DisplayImageOptions getCacheOnDiskOptions() { + return new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(true) + .build(); + } + + public static DisplayImageOptions getCacheInMemoryOptions() { + return new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(false) + .build(); + } + + public static class defaultAlbumArtOnFailed implements ImageLoadingListener { + @Override + public void onLoadingStarted(String imageUri, View view) { + ((ImageView) view).setImageResource(R.drawable.default_album_art); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + ((ImageView) view).setImageResource(R.drawable.default_album_art); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + ((ImageView) view).setImageResource(R.drawable.default_album_art); + } + } + + public static class defaultArtistArtOnFailed implements ImageLoadingListener { + + @Override + public void onLoadingStarted(String imageUri, View view) { + ((ImageView) view).setImageResource(R.drawable.default_artist_image); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + ((ImageView) view).setImageResource(R.drawable.default_artist_image); + } + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + ((ImageView) view).setImageResource(R.drawable.default_artist_image); + } + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/util/InternalStorageUtil.java b/app/src/main/java/com/kabouzeid/materialmusic/util/InternalStorageUtil.java new file mode 100644 index 00000000..2435c99b --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/util/InternalStorageUtil.java @@ -0,0 +1,37 @@ +package com.kabouzeid.materialmusic.util; + +import android.content.Context; +import android.util.Log; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Created by karim on 22.12.14. + */ +public final class InternalStorageUtil { + private static final String TAG = InternalStorageUtil.class.getSimpleName(); + + public static synchronized void writeObject(final Context context, final String key, final Object object) throws IOException { + try { + FileOutputStream fos; + fos = context.openFileOutput(key, Context.MODE_PRIVATE); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(object); + oos.close(); + fos.close(); + } catch (IOException e) { + Log.e(TAG, "Writing Object to internal storage failed! Maybe the Object is not serializable?", e); + } + } + + public static synchronized Object readObject(Context context, String key) throws IOException, + ClassNotFoundException { + FileInputStream fis = context.openFileInput(key); + ObjectInputStream ois = new ObjectInputStream(fis); + return ois.readObject(); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/util/MusicUtil.java b/app/src/main/java/com/kabouzeid/materialmusic/util/MusicUtil.java new file mode 100644 index 00000000..3649035e --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/util/MusicUtil.java @@ -0,0 +1,76 @@ +package com.kabouzeid.materialmusic.util; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.os.Environment; +import android.util.Log; + +import java.io.File; +import java.io.IOException; + +/** + * Created by karim on 29.12.14. + */ +public class MusicUtil { + public static final String TAG = MusicUtil.class.getSimpleName(); + + public static Uri getAlbumArtUri(int album_id) { + final Uri sArtworkUri = Uri + .parse("content://media/external/audio/albumart"); + + return ContentUris.withAppendedId(sArtworkUri, album_id); + } + + public static String getReadableDurationString(long songDurationMillis) { + long minutes = (songDurationMillis / 1000) / 60; + long seconds = (songDurationMillis / 1000) % 60; + return String.format("%02d:%02d", minutes, seconds); + } + + + //iTunes uses for example 1002 for track 2 CD1 or 3011 for track 11 CD3. + //this method converts those values to normal tracknumbers + public static int getFixedTrackNumber(int trackNumberToFix) { + return trackNumberToFix % 1000; + } + + public static void insertAlbumArt(Context context, int albumId, String path) { + ContentResolver contentResolver = context.getContentResolver(); + + Uri artworkUri = Uri.parse("content://media/external/audio/albumart"); + contentResolver.delete(ContentUris.withAppendedId(artworkUri, albumId), null, null); + + ContentValues values = new ContentValues(); + values.put("album_id", albumId); + values.put("_data", path); + + contentResolver.insert(artworkUri, values); + } + + public static void deleteAlbumArt(Context context, int albumId) { + ContentResolver contentResolver = context.getContentResolver(); + Uri localUri = Uri.parse("content://media/external/audio/albumart"); + contentResolver.delete(ContentUris.withAppendedId(localUri, albumId), null, null); + } + + public static File createAlbumArtDir(Context paramContext) { + File albumArtDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/.albumart/"); + if (!albumArtDir.exists()) { + albumArtDir.mkdirs(); + try { + new File(albumArtDir, ".nomedia").createNewFile(); + } catch (IOException e) { + Log.e(TAG, "error while creating .nomedia file", e); + } + } + return albumArtDir; + } + + public static File getAlbumArtFile(Context context, String name) + throws IOException { + return new File(createAlbumArtDir(context), name + System.currentTimeMillis()); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/util/Util.java b/app/src/main/java/com/kabouzeid/materialmusic/util/Util.java new file mode 100644 index 00000000..fc9e0f57 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/util/Util.java @@ -0,0 +1,179 @@ +package com.kabouzeid.materialmusic.util; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.TypedValue; +import android.view.Window; +import android.view.WindowManager; + +import com.kabouzeid.materialmusic.App; +import com.kabouzeid.materialmusic.R; +import com.kabouzeid.materialmusic.misc.AppKeys; + +/** + * Created by karim on 12.12.14. + */ +public class Util { + public static int resolveDrawable(Context context, int drawable) { + TypedArray a = context.obtainStyledAttributes(new int[]{drawable}); + int resId = a.getResourceId(0, 0); + a.recycle(); + return resId; + } + + public static int resolveColor(Context context, int color) { + TypedArray a = context.obtainStyledAttributes(new int[]{color}); + int resId = a.getColor(0, context.getResources().getColor(R.color.materialmusic_color)); + a.recycle(); + return resId; + } + + public static boolean isWindowTranslucent(Context context) { + TypedArray a = context.obtainStyledAttributes(new int[]{android.R.attr.windowTranslucentStatus}); + boolean result = a.getBoolean(0, false); + a.recycle(); + return result; + } + + public static int getActionBarSize(Context context) { + TypedValue typedValue = new TypedValue(); + int[] textSizeAttr = new int[]{R.attr.actionBarSize}; + int indexOfAttrTextSize = 0; + TypedArray a = context.obtainStyledAttributes(typedValue.data, textSizeAttr); + int actionBarSize = a.getDimensionPixelSize(indexOfAttrTextSize, -1); + a.recycle(); + return actionBarSize; + } + + public static int getStatusBarHeight(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = context.getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + public static int getNavigationBarHeight(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = context.getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + @TargetApi(19) + public static void setNavBarTranslucent(Window window, boolean translucent) { + if (translucent) { + window.setFlags( + WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, + WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + return; + } + + final WindowManager.LayoutParams attrs = window + .getAttributes(); + attrs.flags &= (~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + window.setAttributes(attrs); + window.clearFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + + @TargetApi(19) + public static void setStatusBarTranslucent(Window window, boolean translucent) { + if (translucent) { + window.setFlags( + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + return; + } + + final WindowManager.LayoutParams attrs = window + .getAttributes(); + attrs.flags &= (~WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.setAttributes(attrs); + window.clearFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } + + public static final boolean isOnline(final Context context) { + if (context == null) { + return false; + } + + boolean state = false; + final boolean onlyOnWifi = ((App) context.getApplicationContext()).getDefaultSharedPreferences().getBoolean(AppKeys.SP_ONLY_ON_WIFI, true); + + /* Monitor network connections */ + final ConnectivityManager connectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + + /* Wi-Fi connection */ + final NetworkInfo wifiNetwork = connectivityManager + .getNetworkInfo(ConnectivityManager.TYPE_WIFI); + if (wifiNetwork != null) { + state = wifiNetwork.isConnectedOrConnecting(); + } + + /* Mobile data connection */ + final NetworkInfo mbobileNetwork = connectivityManager + .getNetworkInfo(ConnectivityManager.TYPE_MOBILE); + if (mbobileNetwork != null) { + if (!onlyOnWifi) { + state = mbobileNetwork.isConnectedOrConnecting(); + } + } + + /* Other networks */ + final NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); + if (activeNetwork != null) { + if (!onlyOnWifi) { + state = activeNetwork.isConnectedOrConnecting(); + } + } + + return state; + } + + public static String getFileSizeString(long sizeInBytes) { + long fileSizeInKB = sizeInBytes / 1024; + long fileSizeInMB = fileSizeInKB / 1024; + return fileSizeInMB + " MB"; + } + + public static String getFilePathFromContentProviderUri(Context context, Uri uri) { + String[] projection = {MediaStore.MediaColumns.DATA}; + Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); + if (cursor == null) return null; + int column_index = cursor.getColumnIndexOrThrow(projection[0]); + cursor.moveToFirst(); + String path = cursor.getString(column_index); + cursor.close(); + return path; + } + + private static int albumArtSize = 600; + + public static Bitmap getAlbumArtScaledBitmap(final Bitmap bitmap, boolean keepAspectRatio) { + if (keepAspectRatio) { + double aspectRatio = (double) bitmap.getHeight() / (double) bitmap.getWidth(); + int targetWidth = albumArtSize; + int targetHeight = (int) (targetWidth * aspectRatio); + return Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, false); + } else { + return getScaledBitmap(bitmap); + } + } + + private static Bitmap getScaledBitmap(final Bitmap bitmap) { + return Bitmap.createScaledBitmap(bitmap, albumArtSize, albumArtSize, false); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/util/ViewUtil.java b/app/src/main/java/com/kabouzeid/materialmusic/util/ViewUtil.java new file mode 100644 index 00000000..d04ad965 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/util/ViewUtil.java @@ -0,0 +1,98 @@ +package com.kabouzeid.materialmusic.util; + +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.os.Build; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.PathInterpolator; +import android.widget.ListAdapter; +import android.widget.ListView; + +/** + * Created by karim on 06.12.14. + */ +public class ViewUtil { + public final static int DEFAULT_COLOR_ANIMATION_DURATION = 1000; + + public static void disableViews(ViewGroup layout) { + for (int i = 0; i < layout.getChildCount(); i++) { + View child = layout.getChildAt(i); + if (child instanceof ViewGroup) { + disableViews((ViewGroup) child); + } else { + child.setEnabled(false); + } + } + } + + public static void enableViews(ViewGroup layout) { + for (int i = 0; i < layout.getChildCount(); i++) { + View child = layout.getChildAt(i); + if (child instanceof ViewGroup) { + enableViews((ViewGroup) child); + } else { + child.setEnabled(true); + } + } + } + + public static void setListViewHeightBasedOnChildren(ListView listView) { + ListAdapter listAdapter = listView.getAdapter(); + if (listAdapter == null) + return; + + int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.UNSPECIFIED); + int totalHeight = 0; + View view = null; + for (int i = 0; i < listAdapter.getCount(); i++) { + view = listAdapter.getView(i, view, listView); + if (i == 0) + view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT)); + + view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED); + totalHeight += view.getMeasuredHeight(); + } + ViewGroup.LayoutParams params = listView.getLayoutParams(); + params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1)); + listView.setLayoutParams(params); + listView.requestLayout(); + } + + public static void animateViewColor(final View v, final int startColor, final int endColor) { + animateViewColor(v, startColor, endColor, DEFAULT_COLOR_ANIMATION_DURATION); + } + + public static void animateViewColor(final View v, final int startColor, final int endColor, final int duration) { + ObjectAnimator animator = ObjectAnimator.ofObject(v, "backgroundColor", + new ArgbEvaluator(), startColor, endColor); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + animator.setInterpolator(new PathInterpolator(0.4f, 0f, 1f, 1f)); + } + animator.setDuration(duration); + animator.start(); + } + + public static void setBackgroundAlpha(View view, float alpha, int baseColor) { + int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24; + int rgb = 0x00ffffff & baseColor; + view.setBackgroundColor(a + rgb); + } + + public static void addOnGlobalLayoutListener(final View view, final Runnable runnable) { + ViewTreeObserver vto = view.getViewTreeObserver(); + vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + runnable.run(); + } + }); + } +} diff --git a/app/src/main/java/com/kabouzeid/materialmusic/view/SquareImageView.java b/app/src/main/java/com/kabouzeid/materialmusic/view/SquareImageView.java new file mode 100644 index 00000000..75cb9375 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/materialmusic/view/SquareImageView.java @@ -0,0 +1,29 @@ +package com.kabouzeid.materialmusic.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * Created by karim on 22.11.14. + */ +public class SquareImageView extends ImageView { + + public SquareImageView(Context context) { + super(context); + } + + public SquareImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquareImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, widthMeasureSpec); + } + +} diff --git a/app/src/main/res/drawable-hdpi/default_album_art.png b/app/src/main/res/drawable-hdpi/default_album_art.png new file mode 100644 index 00000000..5b82f3d7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/default_album_art.png differ diff --git a/app/src/main/res/drawable-hdpi/drawer_shadow.9.png b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png new file mode 100644 index 00000000..236bff55 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_drawer.png b/app/src/main/res/drawable-hdpi/ic_drawer.png new file mode 100644 index 00000000..c59f601c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_drawer.png differ diff --git a/app/src/main/res/drawable-mdpi/default_album_art.png b/app/src/main/res/drawable-mdpi/default_album_art.png new file mode 100644 index 00000000..577a955c Binary files /dev/null and b/app/src/main/res/drawable-mdpi/default_album_art.png differ diff --git a/app/src/main/res/drawable-mdpi/drawer_shadow.9.png b/app/src/main/res/drawable-mdpi/drawer_shadow.9.png new file mode 100644 index 00000000..ffe3a28d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/drawer_shadow.9.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_drawer.png b/app/src/main/res/drawable-mdpi/ic_drawer.png new file mode 100644 index 00000000..1ed2c56e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_drawer.png differ diff --git a/app/src/main/res/drawable-v21/notification_selector.xml b/app/src/main/res/drawable-v21/notification_selector.xml new file mode 100644 index 00000000..ada911a3 --- /dev/null +++ b/app/src/main/res/drawable-v21/notification_selector.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/default_album_art.png b/app/src/main/res/drawable-xhdpi/default_album_art.png new file mode 100644 index 00000000..60f3f12f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/default_album_art.png differ diff --git a/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png new file mode 100644 index 00000000..fabe9d96 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer.png b/app/src/main/res/drawable-xhdpi/ic_drawer.png new file mode 100644 index 00000000..a5fa74de Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_drawer.png differ diff --git a/app/src/main/res/drawable-xxhdpi/album.png b/app/src/main/res/drawable-xxhdpi/album.png new file mode 100644 index 00000000..bed003af Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/album.png differ diff --git a/app/src/main/res/drawable-xxhdpi/close.png b/app/src/main/res/drawable-xxhdpi/close.png new file mode 100644 index 00000000..380c11cd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/close.png differ diff --git a/app/src/main/res/drawable-xxhdpi/default_album_art.png b/app/src/main/res/drawable-xxhdpi/default_album_art.png new file mode 100644 index 00000000..00241b71 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/default_album_art.png differ diff --git a/app/src/main/res/drawable-xxhdpi/default_artist_image.png b/app/src/main/res/drawable-xxhdpi/default_artist_image.png new file mode 100644 index 00000000..fccf8d17 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/default_artist_image.png differ diff --git a/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png b/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png new file mode 100644 index 00000000..b91e9d7f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/heart.png b/app/src/main/res/drawable-xxhdpi/heart.png new file mode 100644 index 00000000..f4b5f119 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/heart.png differ diff --git a/app/src/main/res/drawable-xxhdpi/heart_outline.png b/app/src/main/res/drawable-xxhdpi/heart_outline.png new file mode 100644 index 00000000..25798845 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/heart_outline.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer.png b/app/src/main/res/drawable-xxhdpi/ic_drawer.png new file mode 100644 index 00000000..9c4685d6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_drawer.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_overflow.png b/app/src/main/res/drawable-xxhdpi/ic_overflow.png new file mode 100755 index 00000000..f7faf571 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_overflow.png differ diff --git a/app/src/main/res/drawable-xxhdpi/interpret.png b/app/src/main/res/drawable-xxhdpi/interpret.png new file mode 100644 index 00000000..a89151b9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/interpret.png differ diff --git a/app/src/main/res/drawable-xxhdpi/music_box.png b/app/src/main/res/drawable-xxhdpi/music_box.png new file mode 100644 index 00000000..7986dc30 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/music_box.png differ diff --git a/app/src/main/res/drawable-xxhdpi/music_box_outline.png b/app/src/main/res/drawable-xxhdpi/music_box_outline.png new file mode 100644 index 00000000..89de4fb3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/music_box_outline.png differ diff --git a/app/src/main/res/drawable-xxhdpi/play_box.png b/app/src/main/res/drawable-xxhdpi/play_box.png new file mode 100644 index 00000000..dfad3ced Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/play_box.png differ diff --git a/app/src/main/res/drawable-xxhdpi/playlist.png b/app/src/main/res/drawable-xxhdpi/playlist.png new file mode 100644 index 00000000..6fea8f72 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/playlist.png differ diff --git a/app/src/main/res/drawable-xxhdpi/settings.png b/app/src/main/res/drawable-xxhdpi/settings.png new file mode 100644 index 00000000..b8acd2d9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/settings.png differ diff --git a/app/src/main/res/drawable-xxhdpi/songs.png b/app/src/main/res/drawable-xxhdpi/songs.png new file mode 100644 index 00000000..63257b92 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/songs.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/default_album_art.png b/app/src/main/res/drawable-xxxhdpi/default_album_art.png new file mode 100644 index 00000000..442f9133 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/default_album_art.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_done_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_done_white_48dp.png new file mode 100755 index 00000000..cfd655c0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_done_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..d6e10d01 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png new file mode 100755 index 00000000..903bd9ab Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png new file mode 100755 index 00000000..f4f713e8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat_grey600_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat_grey600_48dp.png new file mode 100755 index 00000000..237cd896 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_repeat_grey600_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat_one_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat_one_white_48dp.png new file mode 100755 index 00000000..0d7ca9c3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_repeat_one_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_repeat_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white_48dp.png new file mode 100755 index 00000000..1dc6a5dd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_repeat_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_shuffle_grey600_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_shuffle_grey600_48dp.png new file mode 100755 index 00000000..a5ab732d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_shuffle_grey600_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_shuffle_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_shuffle_white_48dp.png new file mode 100755 index 00000000..08b8ba5a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_shuffle_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png new file mode 100755 index 00000000..47dcc949 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_skip_next_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png new file mode 100755 index 00000000..33c0beab Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_skip_previous_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_speaker_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_speaker_white_48dp.png new file mode 100755 index 00000000..43942e12 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_speaker_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/logo.png b/app/src/main/res/drawable-xxxhdpi/logo.png new file mode 100644 index 00000000..cac9baa1 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/logo.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/notification_icon.png b/app/src/main/res/drawable-xxxhdpi/notification_icon.png new file mode 100644 index 00000000..ffc1cbd4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/notification_icon.png differ diff --git a/app/src/main/res/drawable/list_item_activated.xml b/app/src/main/res/drawable/list_item_activated.xml new file mode 100755 index 00000000..603cde48 --- /dev/null +++ b/app/src/main/res/drawable/list_item_activated.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_item_activated_dark.xml b/app/src/main/res/drawable/list_item_activated_dark.xml new file mode 100755 index 00000000..cc568803 --- /dev/null +++ b/app/src/main/res/drawable/list_item_activated_dark.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_item_selected.xml b/app/src/main/res/drawable/list_item_selected.xml new file mode 100755 index 00000000..f11a29f3 --- /dev/null +++ b/app/src/main/res/drawable/list_item_selected.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_item_selected_dark.xml b/app/src/main/res/drawable/list_item_selected_dark.xml new file mode 100755 index 00000000..90fd83f2 --- /dev/null +++ b/app/src/main/res/drawable/list_item_selected_dark.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_selector.xml b/app/src/main/res/drawable/list_selector.xml new file mode 100755 index 00000000..bdc63fd2 --- /dev/null +++ b/app/src/main/res/drawable/list_selector.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/list_selector_dark.xml b/app/src/main/res/drawable/list_selector_dark.xml new file mode 100755 index 00000000..2edbb809 --- /dev/null +++ b/app/src/main/res/drawable/list_selector_dark.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/navigation_drawer_gradient.xml b/app/src/main/res/drawable/navigation_drawer_gradient.xml new file mode 100644 index 00000000..88c9a943 --- /dev/null +++ b/app/src/main/res/drawable/navigation_drawer_gradient.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/transparent.xml b/app/src/main/res/drawable/transparent.xml new file mode 100755 index 00000000..6c2fdb96 --- /dev/null +++ b/app/src/main/res/drawable/transparent.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_music_controller.xml b/app/src/main/res/layout-land/activity_music_controller.xml new file mode 100644 index 00000000..08ac9480 --- /dev/null +++ b/app/src/main/res/layout-land/activity_music_controller.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-v21/notification_playing.xml b/app/src/main/res/layout-v21/notification_playing.xml new file mode 100644 index 00000000..aeb95fee --- /dev/null +++ b/app/src/main/res/layout-v21/notification_playing.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-v21/notification_playing_expanded.xml b/app/src/main/res/layout-v21/notification_playing_expanded.xml new file mode 100644 index 00000000..837c5f5c --- /dev/null +++ b/app/src/main/res/layout-v21/notification_playing_expanded.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_album_detail.xml b/app/src/main/res/layout/activity_album_detail.xml new file mode 100644 index 00000000..13e29b78 --- /dev/null +++ b/app/src/main/res/layout/activity_album_detail.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_album_tag_editor.xml b/app/src/main/res/layout/activity_album_tag_editor.xml new file mode 100644 index 00000000..d1bacf7f --- /dev/null +++ b/app/src/main/res/layout/activity_album_tag_editor.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_artist_detail.xml b/app/src/main/res/layout/activity_artist_detail.xml new file mode 100644 index 00000000..c1dd2f9f --- /dev/null +++ b/app/src/main/res/layout/activity_artist_detail.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..b117c656 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_music_controller.xml b/app/src/main/res/layout/activity_music_controller.xml new file mode 100644 index 00000000..ed95eae1 --- /dev/null +++ b/app/src/main/res/layout/activity_music_controller.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_song_tag_editor.xml b/app/src/main/res/layout/activity_song_tag_editor.xml new file mode 100644 index 00000000..27cd2e70 --- /dev/null +++ b/app/src/main/res/layout/activity_song_tag_editor.xml @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/album_tile.xml b/app/src/main/res/layout/album_tile.xml new file mode 100644 index 00000000..cfa191c4 --- /dev/null +++ b/app/src/main/res/layout/album_tile.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_file_details.xml b/app/src/main/res/layout/dialog_file_details.xml new file mode 100644 index 00000000..f7449010 --- /dev/null +++ b/app/src/main/res/layout/dialog_file_details.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_loading.xml b/app/src/main/res/layout/dialog_loading.xml new file mode 100644 index 00000000..deffa456 --- /dev/null +++ b/app/src/main/res/layout/dialog_loading.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_playlist.xml b/app/src/main/res/layout/dialog_playlist.xml new file mode 100644 index 00000000..192dd246 --- /dev/null +++ b/app/src/main/res/layout/dialog_playlist.xml @@ -0,0 +1,11 @@ + + diff --git a/app/src/main/res/layout/fragment_albumview.xml b/app/src/main/res/layout/fragment_albumview.xml new file mode 100644 index 00000000..09c55d2a --- /dev/null +++ b/app/src/main/res/layout/fragment_albumview.xml @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_artist_view.xml b/app/src/main/res/layout/fragment_artist_view.xml new file mode 100644 index 00000000..954ee290 --- /dev/null +++ b/app/src/main/res/layout/fragment_artist_view.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_drawer.xml b/app/src/main/res/layout/fragment_drawer.xml new file mode 100644 index 00000000..ca9c1070 --- /dev/null +++ b/app/src/main/res/layout/fragment_drawer.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_gridview.xml b/app/src/main/res/layout/fragment_gridview.xml new file mode 100644 index 00000000..f93082cd --- /dev/null +++ b/app/src/main/res/layout/fragment_gridview.xml @@ -0,0 +1,9 @@ + diff --git a/app/src/main/res/layout/fragment_navigation_drawer.xml b/app/src/main/res/layout/fragment_navigation_drawer.xml new file mode 100644 index 00000000..ad4ade72 --- /dev/null +++ b/app/src/main/res/layout/fragment_navigation_drawer.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + +