diff --git a/.idea/RetroMusicPlayer.iml b/.idea/RetroMusicPlayer.iml new file mode 100644 index 00000000..d6ebd480 --- /dev/null +++ b/.idea/RetroMusicPlayer.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..30aa626c --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..2876f8fb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/RetroMusicPlayer.iml b/RetroMusicPlayer.iml new file mode 100644 index 00000000..c5796822 --- /dev/null +++ b/RetroMusicPlayer.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 00000000..b23b9e1a --- /dev/null +++ b/app/app.iml @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..ed8fc97b --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,145 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.1' + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + + renderscriptTargetApi 28 //must match target sdk and build tools + vectorDrawables.useSupportLibrary = true + + applicationId "code.name.monkey.retromusic" + versionCode 203 + versionName 'R - 1.6.400' + + multiDexEnabled true + + buildConfigField("String", "LASTFM_API_KEY", "\"${getProperty(getProperties('../public.properties'), 'LASTFM_API_KEY')}\"") + buildConfigField("String", "GOOGLE_PLAY_LICENSE_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"") + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + zipAlignEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + + resValue "string", "cast_app_id", "D3C555E1" + } + debug { + applicationIdSuffix '.debug' + versionNameSuffix ' DEBUG' + + resValue "string", "cast_app_id", "D3C555E1" + } + } + flavorDimensions "default" + productFlavors { + normal { + versionCode defaultConfig.versionCode + 10000 + versionName defaultConfig.versionName + "_" + getDate() + dimension "default" + } + } + + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + exclude 'META-INF/rxjava.properties' + } + lintOptions { + disable 'MissingTranslation' + disable 'InvalidPackage' + abortOnError false + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + configurations.all { + resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9' + } + + configurations.all { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + def requested = details.requested + if (requested.group == 'com.android.support') { + if (!requested.name.startsWith("multidex")) { + details.useVersion '27.1.1' + } + } + } + } +} + +def getProperties(String fileName) { + final def Properties properties = new Properties() + def file = file(fileName) + if (file.exists()) { + file.withInputStream { stream -> properties.load(stream) } + } + return properties +} + +static def getProperty(Properties properties, String name) { + return properties.getProperty(name) ?: "$name missing" +} + +static def getDate() { + new Date().format('yyyyMMdd') +} + +ext { + supportLibVersion = "27.1.1" + firebase = "11.8.0" + retrofit = "2.3.0" + butterKnife = "8.8.1" + materialDialog = "0.9.6.0" +} +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:multidex:1.0.3' + implementation "com.android.support:appcompat-v7:$supportLibVersion" + implementation "com.android.support:recyclerview-v7:$supportLibVersion" + implementation "com.android.support:gridlayout-v7:$supportLibVersion" + implementation "com.android.support:cardview-v7:$supportLibVersion" + implementation "com.android.support:palette-v7:$supportLibVersion" + implementation "com.android.support:design:$supportLibVersion" + implementation "com.android.support:support-annotations:$supportLibVersion" + implementation "com.android.support:preference-v7:$supportLibVersion" + implementation "com.android.support:preference-v14:$supportLibVersion" + implementation "com.squareup.retrofit2:retrofit:$retrofit" + implementation "com.squareup.retrofit2:converter-gson:$retrofit" + implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit" + implementation "com.jakewharton:butterknife:$butterKnife" + annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnife" + implementation "com.afollestad.material-dialogs:core:$materialDialog" + implementation "com.afollestad.material-dialogs:commons:$materialDialog" + implementation 'com.afollestad:material-cab:0.1.12' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'io.reactivex.rxjava2:rxjava:2.1.9' + implementation 'com.github.bumptech.glide:glide:3.8.0' + implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' + implementation 'com.squareup.okhttp3:okhttp:3.10.0' + implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0' + implementation('com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.11.0@aar') { + transitive = true + } + implementation 'com.mpatric:mp3agic:0.8.3' + implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0' + implementation 'com.github.kabouzeid:RecyclerView-FastScroll:1.0.16-kmod' + implementation 'com.anjlab.android.iab.v3:library:1.0.44' + implementation 'jp.wasabeef:glide-transformations:2.0.2' + /*UI Library*/ + implementation 'com.github.jetradarmobile:android-snowfall:1.1.6' + implementation 'uk.co.chrisjenx:calligraphy:2.3.0' + implementation 'me.zhanghai.android.materialprogressbar:library:1.4.2' + implementation 'com.r0adkll:slidableactivity:2.0.6' + /*Backend all*/ + implementation project(':appthemehelper') + implementation 'com.sothree.slidinguppanel:library:3.4.0' + implementation 'org.nanohttpd:nanohttpd:2.3.1' +} diff --git a/app/libs/jaudiotagger-android-2.2.3.jar b/app/libs/jaudiotagger-android-2.2.3.jar new file mode 100644 index 00000000..11a46d34 Binary files /dev/null and b/app/libs/jaudiotagger-android-2.2.3.jar differ diff --git a/app/libs/jsoup-1.11.2.jar b/app/libs/jsoup-1.11.2.jar new file mode 100644 index 00000000..e4be2aed Binary files /dev/null and b/app/libs/jsoup-1.11.2.jar differ diff --git a/app/libs/juniversalchardet-1.0.3.jar b/app/libs/juniversalchardet-1.0.3.jar new file mode 100644 index 00000000..1af703fe Binary files /dev/null and b/app/libs/juniversalchardet-1.0.3.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..7e896c52 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,75 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/hemanths/Library/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 *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-dontwarn java.lang.invoke.* +-dontwarn **$$Lambda$* + +# RetroFit +-dontwarn retrofit.** +-keep class retrofit.** { *; } +-keepattributes Signature +-keepattributes Exceptions +-dontwarn javax.annotation.** + +# Glide +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { + **[] $VALUES; + public *; +} + +# ButterKnife +-keep class butterknife.** { *; } +-dontwarn butterknife.internal.** +-keep class **$$ViewBinder { *; } +-keepclasseswithmembernames class * { + @butterknife.* ; +} +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +-keep class !android.support.v7.internal.view.menu.**,** {*;} + +-dontwarn +-ignorewarnings + +# ------- FastScrollRecycleView START ------- +-keep class com.simplecityapps.recyclerview_fastscroll.views.FastScrollPopup { *; } +# ------- FastScrollRecycleView END ------- + +-keep public class android.support.design.widget.BottomNavigationView { *; } +-keep public class android.support.design.internal.BottomNavigationMenuView { *; } +-keep public class android.support.design.internal.BottomNavigationPresenter { *; } +-keep public class android.support.design.internal.BottomNavigationItemView { *; } + +#-dontwarn android.support.v8.renderscript.* +#-keepclassmembers class android.support.v8.renderscript.RenderScript { +# native *** rsn*(...); +# native *** n*(...); +#} + +#-keep class org.jaudiotagger.** { *; } \ 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..5fa8503e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/fonts/circular_std_black.otf b/app/src/main/assets/fonts/circular_std_black.otf new file mode 100755 index 00000000..c62b210c Binary files /dev/null and b/app/src/main/assets/fonts/circular_std_black.otf differ diff --git a/app/src/main/assets/fonts/circular_std_book.otf b/app/src/main/assets/fonts/circular_std_book.otf new file mode 100755 index 00000000..3a1f1ad8 Binary files /dev/null and b/app/src/main/assets/fonts/circular_std_book.otf differ diff --git a/app/src/main/assets/fonts/product_sans_bold.ttf b/app/src/main/assets/fonts/product_sans_bold.ttf new file mode 100755 index 00000000..d847195c Binary files /dev/null and b/app/src/main/assets/fonts/product_sans_bold.ttf differ diff --git a/app/src/main/assets/fonts/product_sans_regular.ttf b/app/src/main/assets/fonts/product_sans_regular.ttf new file mode 100755 index 00000000..c0442ee2 Binary files /dev/null and b/app/src/main/assets/fonts/product_sans_regular.ttf differ diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html new file mode 100644 index 00000000..115106cb --- /dev/null +++ b/app/src/main/assets/index.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + +

Phonograph by Karim Abou Zeid

+

RxAndroid by RxAndroid authors

+

RxJava by RxJava authors

+

Material Dialogs by Aidan Michael Follestad

+

Calligraphy by RxJava authors

+

Android-Snowfall by JetRadar

+

Android Sliding Up Panelby The Umano Team

+

AOSP Support Librariesby AOSP contributors

+

Butter Knife by Jake Wharton

+

Glide by Sam Judd

+

Retrofit by Square team

+

Material Contextual Action Bar by Aidan Michael Follestad

+

OkHttp by Square team

+

CircleImageView by Henning Dodenhof

+

Transitions Everywhere by Henning Dodenhof

+

MaterialProgressBar by Zhang Hai

+

Android In-App Billing v3 Library by Henning Dodenhof

+

Advanced RecyclerView by Haruki Hasegawa

+

Android-ObservableScrollView by Soichiro Kashima

+

BottomNavigationViewEx by Ittianyu

+

Swipe-Button by EBANX Team

+

Font used(CIRCULAR STD BOOK FONT) by NIELSON CAETANO

+

Icons by Austin Andrews

+

Croller by Harjot Oberai

+

Material Design City Wallpaper

+ + + diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 00000000..924099d5 Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/code/name/monkey/retromusic/Constants.java b/app/src/main/java/code/name/monkey/retromusic/Constants.java new file mode 100644 index 00000000..2252315d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/Constants.java @@ -0,0 +1,48 @@ +package code.name.monkey.retromusic; + +public class Constants { + + public static final String DISCORD_LINK = "https://discord.gg/qTecXXn"; + + public static final String RETRO_MUSIC_PACKAGE_NAME = "code.name.monkey.retromusic"; + public static final String MUSIC_PACKAGE_NAME = "com.android.music"; + public static final String ACTION_TOGGLE_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".togglepause"; + public static final String ACTION_PLAY = RETRO_MUSIC_PACKAGE_NAME + ".play"; + public static final String ACTION_PLAY_PLAYLIST = RETRO_MUSIC_PACKAGE_NAME + ".play.playlist"; + public static final String ACTION_PAUSE = RETRO_MUSIC_PACKAGE_NAME + ".pause"; + public static final String ACTION_STOP = RETRO_MUSIC_PACKAGE_NAME + ".stop"; + public static final String ACTION_SKIP = RETRO_MUSIC_PACKAGE_NAME + ".skip"; + public static final String ACTION_REWIND = RETRO_MUSIC_PACKAGE_NAME + ".rewind"; + public static final String ACTION_QUIT = RETRO_MUSIC_PACKAGE_NAME + ".quitservice"; + public static final String INTENT_EXTRA_PLAYLIST = + RETRO_MUSIC_PACKAGE_NAME + "intentextra.playlist"; + public static final String INTENT_EXTRA_SHUFFLE_MODE = + RETRO_MUSIC_PACKAGE_NAME + ".intentextra.shufflemode"; + public static final String APP_WIDGET_UPDATE = RETRO_MUSIC_PACKAGE_NAME + ".appwidgetupdate"; + public static final String EXTRA_APP_WIDGET_NAME = RETRO_MUSIC_PACKAGE_NAME + "app_widget_name"; + // do not change these three strings as it will break support with other apps (e.g. last.fm scrobbling) + public static final String META_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".metachanged"; + public static final String QUEUE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".queuechanged"; + public static final String PLAY_STATE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".playstatechanged"; + public static final String REPEAT_MODE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".repeatmodechanged"; + public static final String SHUFFLE_MODE_CHANGED = + RETRO_MUSIC_PACKAGE_NAME + ".shufflemodechanged"; + public static final String MEDIA_STORE_CHANGED = RETRO_MUSIC_PACKAGE_NAME + ".mediastorechanged"; + public static final String RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"; + public static final String LUIS_GOMZ_GOOGLE_PLUS = "https://plus.google.com/104046235912044592643"; + public static final String LUIS_GOMZ_TWITTER = "https://www.twitter.com/LuisGmzz"; + public static final String PAYPAL_ME_URL = "https://www.paypal.me/h4h14"; + public static final String GOOGLE_PLUS_COMMUNITY = "https://plus.google.com/communities/110811566242871492162"; + public static final String TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534"; + public static final String GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicApp"; + public static final String BASE_API_URL_KUGOU = "http://lyrics.kugou.com/"; + public static final String TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"; + public static final String USER_PROFILE = "profile.jpg"; + public static final String USER_BANNER = "banner.jpg"; + public static final String APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/"; + public static final String APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"; + public static final String APP_TWITTER_LINK = "https://twitter.com/retromusicapp"; + public static final int CAST_SERVER_PORT = 8080; + public static final String FAQ_LINK = "https://github.com/h4h13/RetroMusicApp/blob/master/FAQ.md"; + public static String LINE_SEPARATOR = System.getProperty("line.separator"); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/Injection.java b/app/src/main/java/code/name/monkey/retromusic/Injection.java new file mode 100644 index 00000000..481613ff --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/Injection.java @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic; + +import code.name.monkey.retromusic.providers.RepositoryImpl; +import code.name.monkey.retromusic.providers.interfaces.Repository; +import code.name.monkey.retromusic.rest.KogouClient; +import code.name.monkey.retromusic.rest.service.KuGouApiService; +import code.name.monkey.retromusic.util.schedulers.BaseSchedulerProvider; +import code.name.monkey.retromusic.util.schedulers.SchedulerProvider; + +public class Injection { + + public static Repository provideRepository() { + return RepositoryImpl.getInstance(); + } + + public static BaseSchedulerProvider provideSchedulerProvider() { + return SchedulerProvider.getInstance(); + } + + public static KuGouApiService provideKuGouApiService() { + return new KogouClient().getApiService(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java b/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java new file mode 100644 index 00000000..cc2abeef --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/RetroApplication.java @@ -0,0 +1,77 @@ +package code.name.monkey.retromusic; + +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.multidex.MultiDexApplication; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager; +import com.anjlab.android.iab.v3.BillingProcessor; +import com.anjlab.android.iab.v3.TransactionDetails; +import uk.co.chrisjenx.calligraphy.CalligraphyConfig; + +public class RetroApplication extends MultiDexApplication { + + public static final String PRO_VERSION_PRODUCT_ID = "pro_version"; + + private static RetroApplication app; + + private BillingProcessor billingProcessor; + + public static RetroApplication getInstance() { + return app; + } + + public static boolean isProVersion() { + return BuildConfig.DEBUG || app.billingProcessor.isPurchased(PRO_VERSION_PRODUCT_ID); + } + + @Override + public void onCreate() { + super.onCreate(); + app = this; + + // default theme + if (!ThemeStore.isConfigured(this, 1)) { + ThemeStore.editTheme(this) + .accentColorRes(R.color.md_green_A200) + .commit(); + } + + CalligraphyConfig.initDefault(new CalligraphyConfig.Builder() + .setDefaultFontPath("fonts/circular_std_book.otf") + .setFontAttrId(R.attr.fontPath) + .build() + ); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + new DynamicShortcutManager(this).initDynamicShortcuts(); + } + + // automatically restores purchases + billingProcessor = new BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSE_KEY, + new BillingProcessor.IBillingHandler() { + @Override + public void onProductPurchased(@NonNull String productId, TransactionDetails details) { + } + + @Override + public void onPurchaseHistoryRestored() { + //Toast.makeText(App.this, R.string.restored_previous_purchase_please_restart, Toast.LENGTH_LONG).show(); + } + + @Override + public void onBillingError(int errorCode, Throwable error) { + } + + @Override + public void onBillingInitialized() { + } + }); + } + + @Override + public void onTerminate() { + super.onTerminate(); + billingProcessor.release(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java new file mode 100644 index 00000000..7c9eef61 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutIconGenerator.java @@ -0,0 +1,71 @@ +package code.name.monkey.retromusic.appshortcuts; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.TypedValue; + +import code.name.monkey.appthemehelper.ThemeStore; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +/** + * @author Adrian Campos + */ +@RequiresApi(Build.VERSION_CODES.N_MR1) +public final class AppShortcutIconGenerator { + public static Icon generateThemedIcon(Context context, int iconId) { + if (PreferenceUtil.getInstance(context).coloredAppShortcuts()){ + return generateUserThemedIcon(context, iconId); + } else { + return generateDefaultThemedIcon(context, iconId); + } + } + + private static Icon generateDefaultThemedIcon(Context context, int iconId) { + // Return an Icon of iconId with default colors + return generateThemedIcon(context, iconId, + context.getColor(R.color.app_shortcut_default_foreground), + context.getColor(R.color.app_shortcut_default_background) + ); + } + + private static Icon generateUserThemedIcon(Context context, int iconId) { + // Get background color from context's theme + final TypedValue typedColorBackground = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedColorBackground, true); + + // Return an Icon of iconId with those colors + return generateThemedIcon(context, iconId, + ThemeStore.accentColor(context), + typedColorBackground.data + ); + } + + private static Icon generateThemedIcon(Context context, int iconId, int foregroundColor, int backgroundColor) { + // Get and tint foreground and background drawables + Drawable vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor); + Drawable backgroundDrawable = RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_app_shortcut_background, backgroundColor); + + // Squash the two drawables together + LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{backgroundDrawable, vectorDrawable}); + + // Return as an Icon + return Icon.createWithBitmap(drawableToBitmap(layerDrawable)); + } + + private static Bitmap drawableToBitmap(Drawable drawable) { + Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java new file mode 100644 index 00000000..85e9c402 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/AppShortcutLauncherActivity.java @@ -0,0 +1,77 @@ +package code.name.monkey.retromusic.appshortcuts; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist; + +import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType; +import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType; +import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType; +import code.name.monkey.retromusic.service.MusicService; + +import static code.name.monkey.retromusic.Constants.*; + +/** + * @author Adrian Campos + */ + +public class AppShortcutLauncherActivity extends Activity { + public static final String KEY_SHORTCUT_TYPE = "code.name.monkey.retromusic.appshortcuts.ShortcutType"; + + public static final int SHORTCUT_TYPE_SHUFFLE_ALL = 0; + public static final int SHORTCUT_TYPE_TOP_TRACKS = 1; + public static final int SHORTCUT_TYPE_LAST_ADDED = 2; + public static final int SHORTCUT_TYPE_NONE = 3; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + int shortcutType = SHORTCUT_TYPE_NONE; + + // Set shortcutType from the intent extras + Bundle extras = getIntent().getExtras(); + if (extras != null) { + //noinspection WrongConstant + shortcutType = extras.getInt(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE); + } + + switch (shortcutType) { + case SHORTCUT_TYPE_SHUFFLE_ALL: + startServiceWithPlaylist(MusicService.SHUFFLE_MODE_SHUFFLE, + new ShuffleAllPlaylist(getApplicationContext())); + DynamicShortcutManager.reportShortcutUsed(this, ShuffleAllShortcutType.getId()); + break; + case SHORTCUT_TYPE_TOP_TRACKS: + startServiceWithPlaylist(MusicService.SHUFFLE_MODE_NONE, + new MyTopTracksPlaylist(getApplicationContext())); + DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.getId()); + break; + case SHORTCUT_TYPE_LAST_ADDED: + startServiceWithPlaylist(MusicService.SHUFFLE_MODE_NONE, + new LastAddedPlaylist(getApplicationContext())); + DynamicShortcutManager.reportShortcutUsed(this, LastAddedShortcutType.getId()); + break; + } + + finish(); + } + + private void startServiceWithPlaylist(int shuffleMode, Playlist playlist) { + Intent intent = new Intent(this, MusicService.class); + intent.setAction(ACTION_PLAY_PLAYLIST); + + Bundle bundle = new Bundle(); + bundle.putParcelable(INTENT_EXTRA_PLAYLIST, playlist); + bundle.putInt(INTENT_EXTRA_SHUFFLE_MODE, shuffleMode); + + intent.putExtras(bundle); + + startService(intent); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java new file mode 100644 index 00000000..5fb28054 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/DynamicShortcutManager.java @@ -0,0 +1,63 @@ +package code.name.monkey.retromusic.appshortcuts; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Build; + +import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType; +import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType; +import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType; + +import java.util.Arrays; +import java.util.List; + +/** + * @author Adrian Campos + */ + +@TargetApi(Build.VERSION_CODES.N_MR1) +public class DynamicShortcutManager { + + private Context context; + private ShortcutManager shortcutManager; + + public DynamicShortcutManager(Context context) { + this.context = context; + shortcutManager = this.context.getSystemService(ShortcutManager.class); + } + + public static ShortcutInfo createShortcut(Context context, String id, String shortLabel, String longLabel, Icon icon, Intent intent) { + return new ShortcutInfo.Builder(context, id) + .setShortLabel(shortLabel) + .setLongLabel(longLabel) + .setIcon(icon) + .setIntent(intent) + .build(); + } + + public void initDynamicShortcuts() { + if (shortcutManager.getDynamicShortcuts().size() == 0) { + shortcutManager.setDynamicShortcuts(getDefaultShortcuts()); + } + } + + public void updateDynamicShortcuts() { + shortcutManager.updateShortcuts(getDefaultShortcuts()); + } + + public List getDefaultShortcuts() { + return (Arrays.asList( + new ShuffleAllShortcutType(context).getShortcutInfo(), + new TopTracksShortcutType(context).getShortcutInfo(), + new LastAddedShortcutType(context).getShortcutInfo() + )); + } + + public static void reportShortcutUsed(Context context, String shortcutId){ + context.getSystemService(ShortcutManager.class).reportShortcutUsed(shortcutId); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java new file mode 100644 index 00000000..28f16a7e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/BaseShortcutType.java @@ -0,0 +1,50 @@ +package code.name.monkey.retromusic.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.os.Build; +import android.os.Bundle; + +import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity; + + +/** + * @author Adrian Campos + */ +@TargetApi(Build.VERSION_CODES.N_MR1) +public abstract class BaseShortcutType { + + static final String ID_PREFIX = "code.name.monkey.retromusic.appshortcuts.id."; + + Context context; + + public BaseShortcutType(Context context) { + this.context = context; + } + + static public String getId() { + return ID_PREFIX + "invalid"; + } + + abstract ShortcutInfo getShortcutInfo(); + + /** + * Creates an Intent that will launch MainActivtiy and immediately play {@param songs} in either shuffle or normal mode + * + * @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.) + * @return + */ + Intent getPlaySongsIntent(int shortcutType) { + Intent intent = new Intent(context, AppShortcutLauncherActivity.class); + intent.setAction(Intent.ACTION_VIEW); + + Bundle b = new Bundle(); + b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType); + + intent.putExtras(b); + + return intent; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java new file mode 100644 index 00000000..72db3eeb --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/LastAddedShortcutType.java @@ -0,0 +1,34 @@ +package code.name.monkey.retromusic.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.os.Build; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator; +import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity; + + +/** + * @author Adrian Campos + */ +@TargetApi(Build.VERSION_CODES.N_MR1) +public final class LastAddedShortcutType extends BaseShortcutType { + public LastAddedShortcutType(Context context) { + super(context); + } + + public static String getId() { + return ID_PREFIX + "last_added"; + } + + public ShortcutInfo getShortcutInfo() { + return new ShortcutInfo.Builder(context, getId()) + .setShortLabel(context.getString(R.string.app_shortcut_last_added_short)) + .setLongLabel(context.getString(R.string.app_shortcut_last_added_long)) + .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_last_added)) + .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_LAST_ADDED)) + .build(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java new file mode 100644 index 00000000..be3ba008 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/ShuffleAllShortcutType.java @@ -0,0 +1,35 @@ +package code.name.monkey.retromusic.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.os.Build; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator; +import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity; + + + +/** + * @author Adrian Campos + */ +@TargetApi(Build.VERSION_CODES.N_MR1) +public final class ShuffleAllShortcutType extends BaseShortcutType { + public ShuffleAllShortcutType(Context context) { + super(context); + } + + public static String getId() { + return ID_PREFIX + "shuffle_all"; + } + + public ShortcutInfo getShortcutInfo() { + return new ShortcutInfo.Builder(context, getId()) + .setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short)) + .setLongLabel(context.getString(R.string.app_shortcut_shuffle_all_long)) + .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_shuffle_all)) + .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL)) + .build(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java new file mode 100644 index 00000000..3e78db33 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appshortcuts/shortcuttype/TopTracksShortcutType.java @@ -0,0 +1,35 @@ +package code.name.monkey.retromusic.appshortcuts.shortcuttype; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.os.Build; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator; +import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity; + + + +/** + * @author Adrian Campos + */ +@TargetApi(Build.VERSION_CODES.N_MR1) +public final class TopTracksShortcutType extends BaseShortcutType { + public TopTracksShortcutType(Context context) { + super(context); + } + + public static String getId() { + return ID_PREFIX + "top_tracks"; + } + + public ShortcutInfo getShortcutInfo() { + return new ShortcutInfo.Builder(context, getId()) + .setShortLabel(context.getString(R.string.app_shortcut_top_tracks_short)) + .setLongLabel(context.getString(R.string.app_shortcut_top_tracks_long)) + .setIcon(AppShortcutIconGenerator.generateThemedIcon(context, R.drawable.ic_app_shortcut_top_tracks)) + .setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_TOP_TRACKS)) + .build(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java new file mode 100644 index 00000000..4b90e941 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.java @@ -0,0 +1,171 @@ +package code.name.monkey.retromusic.appwidgets; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.RetroUtil; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; + +public class AppWidgetBig extends BaseAppWidget { + + public static final String NAME = "app_widget_big"; + + private static AppWidgetBig mInstance; + private Target target; // for cancellation + + public static synchronized AppWidgetBig getInstance() { + if (mInstance == null) { + mInstance = new AppWidgetBig(); + } + return mInstance; + } + + /** + * Initialize given widgets to default state, where we launch Music on default click and hide + * actions if service not running. + */ + protected void defaultAppWidget(final Context context, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(), + R.layout.app_widget_big); + + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art); + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, + MaterialValueHelper.getPrimaryTextColor(context, false)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, + MaterialValueHelper.getPrimaryTextColor(context, false)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp, + MaterialValueHelper.getPrimaryTextColor(context, false)), 1f)); + + linkButtons(context, appWidgetView); + pushUpdate(context, appWidgetIds, appWidgetView); + } + + /** + * Update all active widget instances by pushing changes + */ + public void performUpdate(final MusicService service, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(), + R.layout.app_widget_big); + + final boolean isPlaying = service.isPlaying(); + final Song song = service.getCurrentSong(); + + // Set the titles and artwork + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE); + appWidgetView.setTextViewText(R.id.title, song.title); + appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song)); + } + + // Set correct drawable for pause state + int playPauseRes = + isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap( + RetroUtil.getTintedVectorDrawable(service, playPauseRes, + MaterialValueHelper.getPrimaryTextColor(service, false)), 1f)); + + // Set prev/next button drawables + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, + MaterialValueHelper.getPrimaryTextColor(service, false)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, + MaterialValueHelper.getPrimaryTextColor(service, false)), 1f)); + + // Link actions buttons to intents + linkButtons(service, appWidgetView); + + // Load the album cover async and push the update on completion + Point p = RetroUtil.getScreenSize(service); + final int widgetImageSize = Math.min(p.x, p.y); + final Context appContext = service.getApplicationContext(); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(appContext), song) + .checkIgnoreMediaStore(appContext) + .asBitmap().build() + .into(new SimpleTarget(widgetImageSize, widgetImageSize) { + @Override + public void onResourceReady(Bitmap resource, + GlideAnimation glideAnimation) { + update(resource); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null); + } + + private void update(@Nullable Bitmap bitmap) { + if (bitmap == null) { + appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art); + } else { + appWidgetView.setImageViewBitmap(R.id.image, bitmap); + } + pushUpdate(appContext, appWidgetIds, appWidgetView); + } + }); + } + }); + } + + /** + * Link up various button actions using {@link PendingIntent}. + */ + private void linkButtons(final Context context, final RemoteViews views) { + Intent action; + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(context, MusicService.class); + + // Home + action = new Intent(context, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + pendingIntent = PendingIntent.getActivity(context, 0, action, 0); + views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent); + + // Previous track + pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName); + views.setOnClickPendingIntent(R.id.button_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName); + views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName); + views.setOnClickPendingIntent(R.id.button_next, pendingIntent); + + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java new file mode 100644 index 00000000..d7c08067 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.java @@ -0,0 +1,194 @@ +package code.name.monkey.retromusic.appwidgets; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v7.graphics.Palette; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.RetroUtil; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; + +public class AppWidgetCard extends BaseAppWidget { + + public static final String NAME = "app_widget_card"; + + private static AppWidgetCard mInstance; + private static int imageSize = 0; + private static float cardRadius = 0f; + private Target target; // for cancellation + + public static synchronized AppWidgetCard getInstance() { + if (mInstance == null) { + mInstance = new AppWidgetCard(); + } + return mInstance; + } + + /** + * Initialize given widgets to default state, where we launch Music on default click and hide + * actions if service not running. + */ + protected void defaultAppWidget(final Context context, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(), + R.layout.app_widget_card); + + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art); + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + + linkButtons(context, appWidgetView); + pushUpdate(context, appWidgetIds, appWidgetView); + } + + /** + * Update all active widget instances by pushing changes + */ + public void performUpdate(final MusicService service, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(), + R.layout.app_widget_card); + + final boolean isPlaying = service.isPlaying(); + final Song song = service.getCurrentSong(); + + // Set the titles and artwork + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE); + appWidgetView.setTextViewText(R.id.title, song.title); + appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song)); + } + + // Set correct drawable for pause state + int playPauseRes = + isPlaying ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap( + RetroUtil.getTintedVectorDrawable(service, playPauseRes, + MaterialValueHelper.getSecondaryTextColor(service, true)), 1f)); + + // Set prev/next button drawables + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, + MaterialValueHelper.getSecondaryTextColor(service, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, + MaterialValueHelper.getSecondaryTextColor(service, true)), 1f)); + + // Link actions buttons to intents + linkButtons(service, appWidgetView); + + if (imageSize == 0) { + imageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_card_image_size); + } + if (cardRadius == 0f) { + cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius); + } + + // Load the album cover async and push the update on completion + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(service), song) + .checkIgnoreMediaStore(service) + .generatePalette(service).build() + .centerCrop() + .into(new SimpleTarget(imageSize, imageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + Palette palette = resource.getPalette(); + update(resource.getBitmap(), palette.getVibrantColor(palette + .getMutedColor(MaterialValueHelper.getSecondaryTextColor(service, true)))); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null, MaterialValueHelper.getSecondaryTextColor(service, true)); + } + + private void update(@Nullable Bitmap bitmap, int color) { + // Set correct drawable for pause state + int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp + : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, + createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f)); + + // Set prev/next button drawables + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, + color), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, + color), 1f)); + + final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap); + final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize, + cardRadius, 0, cardRadius, 0); + appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap); + + pushUpdate(service, appWidgetIds, appWidgetView); + } + }); + } + }); + } + + /** + * Link up various button actions using {@link PendingIntent}. + */ + private void linkButtons(final Context context, final RemoteViews views) { + Intent action; + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(context, MusicService.class); + + // Home + action = new Intent(context, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + pendingIntent = PendingIntent.getActivity(context, 0, action, 0); + views.setOnClickPendingIntent(R.id.image, pendingIntent); + views.setOnClickPendingIntent(R.id.media_titles, pendingIntent); + + // Previous track + pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName); + views.setOnClickPendingIntent(R.id.button_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName); + views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName); + views.setOnClickPendingIntent(R.id.button_next, pendingIntent); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java new file mode 100644 index 00000000..120b9952 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.java @@ -0,0 +1,181 @@ +package code.name.monkey.retromusic.appwidgets; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v7.graphics.Palette; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.RetroUtil; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; + +public class AppWidgetClassic extends BaseAppWidget { + + public static final String NAME = "app_widget_classic"; + + private static AppWidgetClassic mInstance; + private static int imageSize = 0; + private static float cardRadius = 0f; + private Target target; // for cancellation + + public static synchronized AppWidgetClassic getInstance() { + if (mInstance == null) { + mInstance = new AppWidgetClassic(); + } + return mInstance; + } + + /** + * Initialize given widgets to default state, where we launch Music on default click and hide + * actions if service not running. + */ + protected void defaultAppWidget(final Context context, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(), + R.layout.app_widget_classic); + + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art); + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + + linkButtons(context, appWidgetView); + pushUpdate(context, appWidgetIds, appWidgetView); + } + + /** + * Update all active widget instances by pushing changes + */ + public void performUpdate(final MusicService service, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(), + R.layout.app_widget_classic); + + final boolean isPlaying = service.isPlaying(); + final Song song = service.getCurrentSong(); + + // Set the titles and artwork + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE); + appWidgetView.setTextViewText(R.id.title, song.title); + appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song)); + } + + // Link actions buttons to intents + linkButtons(service, appWidgetView); + + if (imageSize == 0) { + imageSize = service.getResources() + .getDimensionPixelSize(R.dimen.app_widget_classic_image_size); + } + if (cardRadius == 0f) { + cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius); + } + + // Load the album cover async and push the update on completion + final Context appContext = service.getApplicationContext(); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(appContext), song) + .checkIgnoreMediaStore(appContext) + .generatePalette(service).build() + .centerCrop() + .into(new SimpleTarget(imageSize, imageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + Palette palette = resource.getPalette(); + update(resource.getBitmap(), palette.getVibrantColor(palette + .getMutedColor(MaterialValueHelper.getSecondaryTextColor(appContext, true)))); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null, MaterialValueHelper.getSecondaryTextColor(appContext, true)); + } + + private void update(@Nullable Bitmap bitmap, int color) { + // Set correct drawable for pause state + int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp + : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, + createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f)); + + // Set prev/next button drawables + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, + color), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, + color), 1f)); + + final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap); + final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize, + cardRadius, 0, cardRadius, 0); + appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap); + + pushUpdate(appContext, appWidgetIds, appWidgetView); + } + }); + } + }); + } + + /** + * Link up various button actions using {@link PendingIntent}. + */ + private void linkButtons(final Context context, final RemoteViews views) { + Intent action; + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(context, MusicService.class); + + // Home + action = new Intent(context, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + pendingIntent = PendingIntent.getActivity(context, 0, action, 0); + views.setOnClickPendingIntent(R.id.image, pendingIntent); + views.setOnClickPendingIntent(R.id.media_titles, pendingIntent); + + // Previous track + pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName); + views.setOnClickPendingIntent(R.id.button_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName); + views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName); + views.setOnClickPendingIntent(R.id.button_next, pendingIntent); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java new file mode 100644 index 00000000..1d80a2d1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.java @@ -0,0 +1,186 @@ +package code.name.monkey.retromusic.appwidgets; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v7.graphics.Palette; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.RetroUtil; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; + +public class AppWidgetSmall extends BaseAppWidget { + + public static final String NAME = "app_widget_small"; + + private static AppWidgetSmall mInstance; + private static int imageSize = 0; + private static float cardRadius = 0f; + private Target target; // for cancellation + + public static synchronized AppWidgetSmall getInstance() { + if (mInstance == null) { + mInstance = new AppWidgetSmall(); + } + return mInstance; + } + + /** + * Initialize given widgets to default state, where we launch Music on default click and hide + * actions if service not running. + */ + protected void defaultAppWidget(final Context context, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(context.getPackageName(), + R.layout.app_widget_small); + + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + appWidgetView.setImageViewResource(R.id.image, R.drawable.default_album_art); + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_next_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_skip_previous_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, createBitmap( + RetroUtil.getTintedVectorDrawable(context, R.drawable.ic_play_arrow_white_24dp, + MaterialValueHelper.getSecondaryTextColor(context, true)), 1f)); + + linkButtons(context, appWidgetView); + pushUpdate(context, appWidgetIds, appWidgetView); + } + + /** + * Update all active widget instances by pushing changes + */ + public void performUpdate(final MusicService service, final int[] appWidgetIds) { + final RemoteViews appWidgetView = new RemoteViews(service.getPackageName(), + R.layout.app_widget_small); + + final boolean isPlaying = service.isPlaying(); + final Song song = service.getCurrentSong(); + + // Set the titles and artwork + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) { + appWidgetView.setTextViewText(R.id.text_separator, ""); + } else { + appWidgetView.setTextViewText(R.id.text_separator, "•"); + } + + appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE); + appWidgetView.setTextViewText(R.id.title, song.title); + appWidgetView.setTextViewText(R.id.text, song.artistName); + } + + // Link actions buttons to intents + linkButtons(service, appWidgetView); + + if (imageSize == 0) { + imageSize = service.getResources().getDimensionPixelSize(R.dimen.app_widget_small_image_size); + } + if (cardRadius == 0f) { + cardRadius = service.getResources().getDimension(R.dimen.app_widget_card_radius); + } + + // Load the album cover async and push the update on completion + final Context appContext = service.getApplicationContext(); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(appContext), song) + .checkIgnoreMediaStore(appContext) + .generatePalette(service).build() + .centerCrop() + .into(new SimpleTarget(imageSize, imageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + Palette palette = resource.getPalette(); + update(resource.getBitmap(), palette.getVibrantColor(palette + .getMutedColor(MaterialValueHelper.getSecondaryTextColor(appContext, true)))); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null, MaterialValueHelper.getSecondaryTextColor(appContext, true)); + } + + private void update(@Nullable Bitmap bitmap, int color) { + // Set correct drawable for pause state + int playPauseRes = isPlaying ? R.drawable.ic_pause_white_24dp + : R.drawable.ic_play_arrow_white_24dp; + appWidgetView.setImageViewBitmap(R.id.button_toggle_play_pause, + createBitmap(RetroUtil.getTintedVectorDrawable(service, playPauseRes, color), 1f)); + + // Set prev/next button drawables + appWidgetView.setImageViewBitmap(R.id.button_next, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, + color), 1f)); + appWidgetView.setImageViewBitmap(R.id.button_prev, createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, + color), 1f)); + + final Drawable image = getAlbumArtDrawable(service.getResources(), bitmap); + final Bitmap roundedBitmap = createRoundedBitmap(image, imageSize, imageSize, + cardRadius, 0, 0, 0); + appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap); + + pushUpdate(appContext, appWidgetIds, appWidgetView); + } + }); + } + }); + } + + /** + * Link up various button actions using {@link PendingIntent}. + */ + private void linkButtons(final Context context, final RemoteViews views) { + Intent action; + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(context, MusicService.class); + + // Home + action = new Intent(context, MainActivity.class); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + pendingIntent = PendingIntent.getActivity(context, 0, action, 0); + views.setOnClickPendingIntent(R.id.image, pendingIntent); + views.setOnClickPendingIntent(R.id.media_titles, pendingIntent); + + // Previous track + pendingIntent = buildPendingIntent(context, Constants.ACTION_REWIND, serviceName); + views.setOnClickPendingIntent(R.id.button_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(context, Constants.ACTION_TOGGLE_PAUSE, serviceName); + views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(context, Constants.ACTION_SKIP, serviceName); + views.setOnClickPendingIntent(R.id.button_next, pendingIntent); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java new file mode 100644 index 00000000..38fb2989 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/BootReceiver.java @@ -0,0 +1,32 @@ +package code.name.monkey.retromusic.appwidgets; + +import android.appwidget.AppWidgetManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import code.name.monkey.retromusic.service.MusicService; + + +/** + * @author Eugene Cheung (arkon) + */ +public class BootReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); + + // Start music service if there are any existing widgets + if (widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetBig.class)).length > 0 || + widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetClassic.class)).length > 0 || + widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetSmall.class)).length > 0 || + widgetManager.getAppWidgetIds(new ComponentName(context, AppWidgetCard.class)).length > 0) { + final Intent serviceIntent = new Intent(context, MusicService.class); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // not allowed on Oreo + context.startService(serviceIntent); + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java new file mode 100644 index 00000000..b2944cb4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.java @@ -0,0 +1,162 @@ +package code.name.monkey.retromusic.appwidgets.base; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.TextUtils; +import android.widget.RemoteViews; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; + +public abstract class BaseAppWidget extends AppWidgetProvider { + + public static final String NAME = "app_widget"; + + protected static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { + Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), + (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); + drawable.draw(c); + return bitmap; + } + + protected static Bitmap createRoundedBitmap(Drawable drawable, int width, int height, float tl, + float tr, float bl, float br) { + if (drawable == null) { + return null; + } + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + drawable.setBounds(0, 0, width, height); + drawable.draw(c); + + Bitmap rounded = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + Canvas canvas = new Canvas(rounded); + Paint paint = new Paint(); + paint.setShader( + new BitmapShader(bitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP)); + paint.setAntiAlias(true); + canvas.drawPath(composeRoundedRectPath(new RectF(0, 0, width, height), tl, tr, bl, br), paint); + + return rounded; + } + + protected static Path composeRoundedRectPath(RectF rect, float tl, float tr, float bl, float br) { + Path path = new Path(); + tl = tl < 0 ? 0 : tl; + tr = tr < 0 ? 0 : tr; + bl = bl < 0 ? 0 : bl; + br = br < 0 ? 0 : br; + + path.moveTo(rect.left + tl, rect.top); + path.lineTo(rect.right - tr, rect.top); + path.quadTo(rect.right, rect.top, rect.right, rect.top + tr); + path.lineTo(rect.right, rect.bottom - br); + path.quadTo(rect.right, rect.bottom, rect.right - br, rect.bottom); + path.lineTo(rect.left + bl, rect.bottom); + path.quadTo(rect.left, rect.bottom, rect.left, rect.bottom - bl); + path.lineTo(rect.left, rect.top + tl); + path.quadTo(rect.left, rect.top, rect.left + tl, rect.top); + path.close(); + + return path; + } + + /** + * {@inheritDoc} + */ + @Override + public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, + final int[] appWidgetIds) { + defaultAppWidget(context, appWidgetIds); + final Intent updateIntent = new Intent(Constants.APP_WIDGET_UPDATE); + updateIntent.putExtra(Constants.EXTRA_APP_WIDGET_NAME, NAME); + updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + context.sendBroadcast(updateIntent); + } + + /** + * Handle a change notification coming over from {@link MusicService} + */ + public void notifyChange(final MusicService service, final String what) { + if (hasInstances(service)) { + if (Constants.META_CHANGED.equals(what) || Constants.PLAY_STATE_CHANGED.equals(what)) { + performUpdate(service, null); + } + } + } + + protected void pushUpdate(final Context context, final int[] appWidgetIds, + final RemoteViews views) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + if (appWidgetIds != null) { + appWidgetManager.updateAppWidget(appWidgetIds, views); + } else { + appWidgetManager.updateAppWidget(new ComponentName(context, getClass()), views); + } + } + + /** + * Check against {@link AppWidgetManager} if there are any instances of this widget. + */ + protected boolean hasInstances(final Context context) { + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + final int[] mAppWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, + getClass())); + return mAppWidgetIds.length > 0; + } + + protected PendingIntent buildPendingIntent(Context context, final String action, + final ComponentName serviceName) { + Intent intent = new Intent(action); + intent.setComponent(serviceName); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return PendingIntent.getForegroundService(context, 0, intent, 0); + } else { + return PendingIntent.getService(context, 0, intent, 0); + } + } + + abstract protected void defaultAppWidget(final Context context, final int[] appWidgetIds); + + abstract public void performUpdate(final MusicService service, final int[] appWidgetIds); + + protected Drawable getAlbumArtDrawable(final Resources resources, final Bitmap bitmap) { + Drawable image; + if (bitmap == null) { + image = resources.getDrawable(R.drawable.default_album_art); + } else { + image = new BitmapDrawable(resources, bitmap); + } + return image; + } + + protected String getSongArtistAndAlbum(final Song song) { + final StringBuilder builder = new StringBuilder(); + builder.append(song.artistName); + if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) { + builder.append(" • "); + } + builder.append(song.albumName); + return builder.toString(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java new file mode 100644 index 00000000..bd13a4e9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/AddToPlaylistDialog.java @@ -0,0 +1,94 @@ +package code.name.monkey.retromusic.dialogs; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.loaders.PlaylistLoader; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.PlaylistsUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + +/** + * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) + */ +public class AddToPlaylistDialog extends RoundedBottomSheetDialogFragment implements AdapterView.OnItemClickListener { + + @BindView(R.id.playlists) + ListView playlist; + @BindView(R.id.title) + TextView title; + List playlists; + + @NonNull + public static AddToPlaylistDialog create(Song song) { + ArrayList list = new ArrayList<>(); + list.add(song); + return create(list); + } + + @NonNull + public static AddToPlaylistDialog create(ArrayList songs) { + AddToPlaylistDialog dialog = new AddToPlaylistDialog(); + Bundle args = new Bundle(); + args.putParcelableArrayList("songs", songs); + dialog.setArguments(args); + return dialog; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_add_to_playlist, container, false); + ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ArrayAdapter playlistAdapter = new ArrayAdapter<>(getActivity(), R.layout.simple_list_item); + playlists = PlaylistLoader.getAllPlaylists(getActivity()).blockingFirst(); + playlistAdapter.add(getActivity().getResources().getString(R.string.action_new_playlist)); + + for (int i = 1; i < playlists.size(); i++) { + playlistAdapter.add(playlists.get(i - 1).name); + playlistAdapter.notifyDataSetChanged(); + } + + this.playlist.setAdapter(playlistAdapter); + this.playlist.setOnItemClickListener(this); + } + + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + //noinspection unchecked + final ArrayList songs = getArguments().getParcelableArrayList("songs"); + + if (songs == null) { + return; + } + if (i == 0) { + dismiss(); + CreatePlaylistDialog.create(songs) + .show(getActivity().getSupportFragmentManager(), "ADD_TO_PLAYLIST"); + } else { + dismiss(); + PlaylistsUtil.addToPlaylist(getActivity(), songs, playlists.get(i - 1).id, true); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java new file mode 100644 index 00000000..4602d50b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/BlacklistFolderChooserDialog.java @@ -0,0 +1,159 @@ +package code.name.monkey.retromusic.dialogs; + +import android.Manifest; +import android.app.Dialog; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.DialogFragment; +import android.view.View; + +import com.afollestad.materialdialogs.MaterialDialog; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import code.name.monkey.retromusic.R; + +/** + * @author Aidan Follestad (afollestad), modified by Karim Abou Zeid + */ +public class BlacklistFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback { + + private File parentFolder; + private File[] parentContents; + private boolean canGoUp = false; + + private FolderCallback callback; + + String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath(); + + private String[] getContentsArray() { + if (parentContents == null) { + if (canGoUp) { + return new String[]{".."}; + } + return new String[]{}; + } + String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)]; + if (canGoUp) { + results[0] = ".."; + } + for (int i = 0; i < parentContents.length; i++) { + results[canGoUp ? i + 1 : i] = parentContents[i].getName(); + } + return results; + } + + private File[] listFiles() { + File[] contents = parentFolder.listFiles(); + List results = new ArrayList<>(); + if (contents != null) { + for (File fi : contents) { + if (fi.isDirectory()) { + results.add(fi); + } + } + Collections.sort(results, new FolderSorter()); + return results.toArray(new File[results.size()]); + } + return null; + } + + public static BlacklistFolderChooserDialog create() { + return new BlacklistFolderChooserDialog(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + return new MaterialDialog.Builder(getActivity()) + .title(R.string.md_error_label) + .content(R.string.md_storage_perm_error) + .positiveText(android.R.string.ok) + .build(); + } + if (savedInstanceState == null) { + savedInstanceState = new Bundle(); + } + if (!savedInstanceState.containsKey("current_path")) { + savedInstanceState.putString("current_path", initialPath); + } + parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator)); + checkIfCanGoUp(); + parentContents = listFiles(); + MaterialDialog.Builder builder = + new MaterialDialog.Builder(getActivity()) + .title(parentFolder.getAbsolutePath()) + .items((CharSequence[]) getContentsArray()) + .itemsCallback(this) + .autoDismiss(false) + .onPositive((dialog, which) -> { + dismiss(); + callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder); + }) + .onNegative((materialDialog, dialogAction) -> dismiss()) + .positiveText(R.string.add_action) + .negativeText(android.R.string.cancel); + return builder.build(); + } + + @Override + public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) { + if (canGoUp && i == 0) { + parentFolder = parentFolder.getParentFile(); + if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { + parentFolder = parentFolder.getParentFile(); + } + checkIfCanGoUp(); + } else { + parentFolder = parentContents[canGoUp ? i - 1 : i]; + canGoUp = true; + if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { + parentFolder = Environment.getExternalStorageDirectory(); + } + } + reload(); + } + + private void checkIfCanGoUp() { + canGoUp = parentFolder.getParent() != null; + } + + private void reload() { + parentContents = listFiles(); + MaterialDialog dialog = (MaterialDialog) getDialog(); + dialog.setTitle(parentFolder.getAbsolutePath()); + dialog.setItems((CharSequence[]) getContentsArray()); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString("current_path", parentFolder.getAbsolutePath()); + } + + public void setCallback(FolderCallback callback) { + this.callback = callback; + } + + public interface FolderCallback { + void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder); + } + + private static class FolderSorter implements Comparator { + + @Override + public int compare(File lhs, File rhs) { + return lhs.getName().compareTo(rhs.getName()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java new file mode 100644 index 00000000..2358c2aa --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ClearSmartPlaylistDialog.java @@ -0,0 +1,52 @@ +package code.name.monkey.retromusic.dialogs; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.text.Html; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; + + +public class ClearSmartPlaylistDialog extends DialogFragment { + + @NonNull + public static ClearSmartPlaylistDialog create(AbsSmartPlaylist playlist) { + ClearSmartPlaylistDialog dialog = new ClearSmartPlaylistDialog(); + Bundle args = new Bundle(); + args.putParcelable("playlist", playlist); + dialog.setArguments(args); + return dialog; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + //noinspection unchecked + final AbsSmartPlaylist playlist = getArguments().getParcelable("playlist"); + int title = R.string.clear_playlist_title; + //noinspection ConstantConditions + CharSequence content = Html.fromHtml(getString(R.string.clear_playlist_x, playlist.name)); + + return new MaterialDialog.Builder(getActivity()) + .title(title) + .content(content) + .positiveText(R.string.clear_action) + .negativeText(android.R.string.cancel) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + if (getActivity() == null) { + return; + } + playlist.clear(getActivity()); + } + }) + .build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java new file mode 100644 index 00000000..a09cf4cf --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.java @@ -0,0 +1,103 @@ +package code.name.monkey.retromusic.dialogs; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetDialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import java.util.ArrayList; +import java.util.Objects; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.PlaylistsUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + +/** + * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) + */ +public class CreatePlaylistDialog extends RoundedBottomSheetDialogFragment { + + @BindView(R.id.option_1) + EditText playlistName; + @BindView(R.id.action_cancel) + Button actionCancel; + @BindView(R.id.action_create) + Button actionCreate; + + @NonNull + public static CreatePlaylistDialog create() { + return create((Song) null); + } + + @NonNull + public static CreatePlaylistDialog create(@Nullable Song song) { + ArrayList list = new ArrayList<>(); + if (song != null) { + list.add(song); + } + return create(list); + } + + @NonNull + public static CreatePlaylistDialog create(ArrayList songs) { + CreatePlaylistDialog dialog = new CreatePlaylistDialog(); + Bundle args = new Bundle(); + args.putParcelableArrayList("songs", songs); + dialog.setArguments(args); + return dialog; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_create_playlist, container, false); + ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + int accentColor = ThemeStore.accentColor(Objects.requireNonNull(getContext())); + TintHelper.setTintAuto(playlistName, accentColor, true); + TintHelper.setTintAuto(actionCreate, accentColor, true); + actionCancel.setTextColor(accentColor); + } + + @OnClick({R.id.action_cancel, R.id.action_create}) + void actions(View view) { + switch (view.getId()) { + case R.id.action_cancel: + dismiss(); + break; + case R.id.action_create: + if (getActivity() == null) { + return; + } + if (!playlistName.getText().toString().trim().isEmpty()) { + final int playlistId = PlaylistsUtil + .createPlaylist(getActivity(), playlistName.getText().toString()); + if (playlistId != -1 && getActivity() != null) { + //noinspection unchecked + ArrayList songs = getArguments().getParcelableArrayList("songs"); + if (songs != null) { + PlaylistsUtil.addToPlaylist(getActivity(), songs, playlistId, true); + } + } + } + break; + } + dismiss(); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java new file mode 100644 index 00000000..5e408ca8 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.java @@ -0,0 +1,89 @@ +package code.name.monkey.retromusic.dialogs; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.util.PlaylistsUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + + +public class DeletePlaylistDialog extends RoundedBottomSheetDialogFragment { + + @BindView(R.id.action_delete) + TextView delete; + @BindView(R.id.title) + TextView title; + @BindView(R.id.action_cancel) + TextView cancel; + + @NonNull + public static DeletePlaylistDialog create(Playlist playlist) { + ArrayList list = new ArrayList<>(); + list.add(playlist); + return create(list); + } + + @NonNull + public static DeletePlaylistDialog create(ArrayList playlists) { + DeletePlaylistDialog dialog = new DeletePlaylistDialog(); + Bundle args = new Bundle(); + args.putParcelableArrayList("playlists", playlists); + dialog.setArguments(args); + return dialog; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_delete_playlist, container, false); + ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + //noinspection unchecked + final ArrayList playlists = getArguments().getParcelableArrayList("playlists"); + int title; + CharSequence content; + //noinspection ConstantConditions + if (playlists.size() > 1) { + title = R.string.delete_playlists_title; + content = Html.fromHtml(getString(R.string.delete_x_playlists, playlists.size())); + } else { + title = R.string.delete_playlist_title; + content = Html.fromHtml(getString(R.string.delete_playlist_x, playlists.get(0).name)); + } + this.title.setText(title); + this.delete.setText(content); + } + + @OnClick({R.id.action_cancel, R.id.action_delete}) + void actions(View view) { + final ArrayList playlists = getArguments().getParcelableArrayList("playlists"); + switch (view.getId()) { + case R.id.action_delete: + if (getActivity() == null) + return; + PlaylistsUtil.deletePlaylists(getActivity(), playlists); + break; + default: + } + dismiss(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java new file mode 100644 index 00000000..1bdb83d4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.java @@ -0,0 +1,112 @@ +package code.name.monkey.retromusic.dialogs; + +/* +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.text.Html; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; + +import code.name.monkey.retromusic.util.MusicUtil; + +*/ + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetDialogFragment; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + +/** + * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) + */ + +public class DeleteSongsDialog extends RoundedBottomSheetDialogFragment { + @BindView(R.id.action_delete) + TextView delete; + @BindView(R.id.title) + TextView title; + @BindView(R.id.action_cancel) + TextView cancel; + + @NonNull + public static DeleteSongsDialog create(Song song) { + ArrayList list = new ArrayList<>(); + list.add(song); + return create(list); + } + + @NonNull + public static DeleteSongsDialog create(ArrayList songs) { + DeleteSongsDialog dialog = new DeleteSongsDialog(); + Bundle args = new Bundle(); + args.putParcelableArrayList("songs", songs); + dialog.setArguments(args); + return dialog; + } + + @OnClick({R.id.action_cancel, R.id.action_delete}) + void actions(View view) { + final ArrayList songs = getArguments().getParcelableArrayList("songs"); + switch (view.getId()) { + case R.id.action_delete: + if (getActivity() == null) + return; + MusicUtil.deleteTracks(getActivity(), songs); + break; + default: + } + dismiss(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + //noinspection unchecked + final ArrayList songs = getArguments().getParcelableArrayList("songs"); + int title; + CharSequence content; + if (songs != null && songs.size() > 1) { + title = R.string.delete_songs_title; + content = Html.fromHtml(getString(R.string.delete_x_songs, songs.size())); + } else { + title = R.string.delete_song_title; + content = Html.fromHtml(getString(R.string.delete_song_x, songs.get(0).title)); + } + + this.title.setText(title); + this.delete.setText(content); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_delete_songs, container, false); + ButterKnife.bind(this, layout); + return layout; + } + +} + diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/HomeOptionDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/HomeOptionDialog.java new file mode 100644 index 00000000..11873df9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/HomeOptionDialog.java @@ -0,0 +1,125 @@ +package code.name.monkey.retromusic.dialogs; + +import android.graphics.Bitmap; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatTextView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.io.File; +import java.util.Calendar; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.Compressor; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.CircularImageView; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + +import static code.name.monkey.retromusic.Constants.USER_PROFILE; + +/** + * @author Hemanth S (h4h13). + */ +public class HomeOptionDialog extends RoundedBottomSheetDialogFragment { + private static final String TAG = "HomeOptionDialog"; + Unbinder mUnbinder; + @BindView(R.id.user_image_bottom) + CircularImageView userImageBottom; + @BindView(R.id.title_welcome) + AppCompatTextView titleWelcome; + private CompositeDisposable disposable = new CompositeDisposable(); + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.user_action_details, container, false); + mUnbinder = ButterKnife.bind(this, layout); + return layout; + } + + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + loadImageFromStorage(); + titleWelcome.setText(String.format("%s, %s!", getTimeOfTheDay(), PreferenceUtil.getInstance(getContext()).getUserName())); + } + + private String getTimeOfTheDay() { + String message = getString(R.string.title_good_day); + Calendar c = Calendar.getInstance(); + int timeOfDay = c.get(Calendar.HOUR_OF_DAY); + + if (timeOfDay >= 0 && timeOfDay < 6) { + message = getString(R.string.title_good_night); + } else if (timeOfDay >= 6 && timeOfDay < 12) { + message = getString(R.string.title_good_morning); + } else if (timeOfDay >= 12 && timeOfDay < 16) { + message = getString(R.string.title_good_afternoon); + } else if (timeOfDay >= 16 && timeOfDay < 20) { + message = getString(R.string.title_good_evening); + } else if (timeOfDay >= 20 && timeOfDay < 24) { + message = getString(R.string.title_good_night); + } + return message; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + disposable.clear(); + mUnbinder.unbind(); + } + + @OnClick({R.id.action_about, R.id.user_info_container, R.id.action_folder, R.id.action_settings, R.id.action_sleep_timer}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.user_info_container: + NavigationUtil.goToUserInfo(getActivity()); + break; + case R.id.action_folder: + //getMainActivity().setCurrentFragment(FoldersFragment.newInstance(getContext()), true); + break; + case R.id.action_settings: + NavigationUtil.goToSettings(getActivity()); + break; + case R.id.action_about: + NavigationUtil.goToAbout(getActivity()); + break; + case R.id.action_sleep_timer: + if (getFragmentManager() != null) { + new SleepTimerDialog().show(getFragmentManager(), TAG); + } + break; + } + dismiss(); + } + + private void loadImageFromStorage() { + //noinspection ConstantConditions + disposable.add(new Compressor(getContext()) + .setMaxHeight(300) + .setMaxWidth(300) + .setQuality(75) + .setCompressFormat(Bitmap.CompressFormat.WEBP) + .compressToBitmapAsFlowable( + new File(PreferenceUtil.getInstance(getContext()).getProfileImage(), USER_PROFILE)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(userImageBottom::setImageBitmap, + throwable -> userImageBottom.setImageDrawable(ContextCompat + .getDrawable(getContext(), R.drawable.ic_person_flat)))); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.java new file mode 100644 index 00000000..a3a80176 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.java @@ -0,0 +1,87 @@ +package code.name.monkey.retromusic.dialogs; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetDialogFragment; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.PlaylistSong; +import code.name.monkey.retromusic.util.PlaylistsUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + + +public class RemoveFromPlaylistDialog extends RoundedBottomSheetDialogFragment { + @BindView(R.id.action_remove) + TextView remove; + @BindView(R.id.title) + TextView title; + @BindView(R.id.action_cancel) + TextView cancel; + + @NonNull + public static RemoveFromPlaylistDialog create(PlaylistSong song) { + ArrayList list = new ArrayList<>(); + list.add(song); + return create(list); + } + + @NonNull + public static RemoveFromPlaylistDialog create(ArrayList songs) { + RemoveFromPlaylistDialog dialog = new RemoveFromPlaylistDialog(); + Bundle args = new Bundle(); + args.putParcelableArrayList("songs", songs); + dialog.setArguments(args); + return dialog; + } + + @OnClick({R.id.action_cancel, R.id.action_remove}) + void actions(View view) { + final ArrayList songs = getArguments().getParcelableArrayList("songs"); + switch (view.getId()) { + case R.id.action_remove: + if (getActivity() == null) + return; + PlaylistsUtil.removeFromPlaylist(getActivity(), songs); + break; + default: + } + dismiss(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_remove_from_playlist, container, false); + ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + //noinspection unchecked + final ArrayList songs = getArguments().getParcelableArrayList("songs"); + int title; + CharSequence content; + if (songs.size() > 1) { + title = R.string.remove_songs_from_playlist_title; + content = Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size())); + } else { + title = R.string.remove_song_from_playlist_title; + content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs.get(0).title)); + } + this.remove.setText(content); + this.title.setText(title); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java new file mode 100644 index 00000000..10bdfced --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.java @@ -0,0 +1,79 @@ +package code.name.monkey.retromusic.dialogs; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetDialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import java.util.Objects; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.PlaylistsUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + +/** + * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) + */ +public class RenamePlaylistDialog extends RoundedBottomSheetDialogFragment { + @BindView(R.id.option_1) + EditText playlistName; + @BindView(R.id.action_cancel) + Button cancel; + @BindView(R.id.action_rename) + Button rename; + + @NonNull + public static RenamePlaylistDialog create(long playlistId) { + RenamePlaylistDialog dialog = new RenamePlaylistDialog(); + Bundle args = new Bundle(); + args.putLong("playlist_id", playlistId); + dialog.setArguments(args); + return dialog; + } + + @OnClick({R.id.action_cancel, R.id.action_rename}) + void actions(View view) { + switch (view.getId()) { + case R.id.action_cancel: + dismiss(); + break; + case R.id.action_rename: + if (!playlistName.toString().trim().equals("")) { + long playlistId = getArguments().getLong("playlist_id"); + PlaylistsUtil.renamePlaylist(getActivity(), playlistId, playlistName.toString()); + } + break; + } + dismiss(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_playlist_rename, container, false); + ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + int accentColor = ThemeStore.accentColor(Objects.requireNonNull(getContext())); + TintHelper.setTintAuto(playlistName, accentColor, true); + TintHelper.setTintAuto(rename, accentColor, true); + cancel.setTextColor(accentColor); + + long playlistId = getArguments().getLong("playlist_id"); + playlistName.setText(PlaylistsUtil.getNameForPlaylist(getActivity(), playlistId)); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/ScanMediaFolderChooserDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/ScanMediaFolderChooserDialog.java new file mode 100644 index 00000000..89c8d125 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/ScanMediaFolderChooserDialog.java @@ -0,0 +1 @@ +package code.name.monkey.retromusic.dialogs; import android.Manifest; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.pm.PackageManager; import android.media.MediaScannerConnection; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.view.View; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; import code.name.monkey.retromusic.ui.fragments.mainactivity.folders.FoldersFragment; import code.name.monkey.retromusic.util.PreferenceUtil; /** * @author Aidan Follestad (afollestad), modified by Karim Abou Zeid */ public class ScanMediaFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback { String initialPath = PreferenceUtil.getInstance(getContext()).getStartDirectory().getAbsolutePath(); private File parentFolder; private File[] parentContents; private boolean canGoUp = false; public static ScanMediaFolderChooserDialog create() { return new ScanMediaFolderChooserDialog(); } private static void scanPaths(@NonNull WeakReference activityWeakReference, @NonNull Context applicationContext, @Nullable String[] toBeScanned) { Activity activity = activityWeakReference.get(); if (toBeScanned == null || toBeScanned.length < 1) { Toast.makeText(applicationContext, R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); } else { MediaScannerConnection.scanFile(applicationContext, toBeScanned, null, activity != null ? new UpdateToastMediaScannerCompletionListener(activity, toBeScanned) : null); } } private String[] getContentsArray() { if (parentContents == null) { if (canGoUp) { return new String[]{".."}; } return new String[]{}; } String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)]; if (canGoUp) { results[0] = ".."; } for (int i = 0; i < parentContents.length; i++) { results[canGoUp ? i + 1 : i] = parentContents[i].getName(); } return results; } private File[] listFiles() { File[] contents = parentFolder.listFiles(); List results = new ArrayList<>(); if (contents != null) { for (File fi : contents) { if (fi.isDirectory()) { results.add(fi); } } Collections.sort(results, new FolderSorter()); return results.toArray(new File[results.size()]); } return null; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission( getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { return new MaterialDialog.Builder(getActivity()) .title(R.string.md_error_label) .content(R.string.md_storage_perm_error) .positiveText(android.R.string.ok) .build(); } if (savedInstanceState == null) { savedInstanceState = new Bundle(); } if (!savedInstanceState.containsKey("current_path")) { savedInstanceState.putString("current_path", initialPath); } parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator)); checkIfCanGoUp(); parentContents = listFiles(); MaterialDialog.Builder builder = new MaterialDialog.Builder(getActivity()) .title(parentFolder.getAbsolutePath()) .items((CharSequence[]) getContentsArray()) .itemsCallback(this) .autoDismiss(false) .onPositive((dialog, which) -> { final Context applicationContext = getActivity().getApplicationContext(); final WeakReference activityWeakReference = new WeakReference<>(getActivity()); dismiss(); new FoldersFragment.ListPathsAsyncTask(getActivity(), paths -> scanPaths(activityWeakReference, applicationContext, paths)).execute(new FoldersFragment.ListPathsAsyncTask.LoadingInfo(parentFolder, FoldersFragment.AUDIO_FILE_FILTER)); }) .onNegative((materialDialog, dialogAction) -> dismiss()) .positiveText(R.string.action_scan_directory) .negativeText(android.R.string.cancel); return builder.build(); } @Override public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) { if (canGoUp && i == 0) { parentFolder = parentFolder.getParentFile(); if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { parentFolder = parentFolder.getParentFile(); } checkIfCanGoUp(); } else { parentFolder = parentContents[canGoUp ? i - 1 : i]; canGoUp = true; if (parentFolder.getAbsolutePath().equals("/storage/emulated")) { parentFolder = Environment.getExternalStorageDirectory(); } } reload(); } private void checkIfCanGoUp() { canGoUp = parentFolder.getParent() != null; } private void reload() { parentContents = listFiles(); MaterialDialog dialog = (MaterialDialog) getDialog(); dialog.setTitle(parentFolder.getAbsolutePath()); dialog.setItems((CharSequence[]) getContentsArray()); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("current_path", parentFolder.getAbsolutePath()); } private static class FolderSorter implements Comparator { @Override public int compare(File lhs, File rhs) { return lhs.getName().compareTo(rhs.getName()); } } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.java new file mode 100755 index 00000000..854a06f2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SleepTimerDialog.java @@ -0,0 +1,173 @@ +package code.name.monkey.retromusic.dialogs; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.Locale; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + +import static code.name.monkey.retromusic.Constants.ACTION_QUIT; + +public class SleepTimerDialog extends RoundedBottomSheetDialogFragment { + @BindView(R.id.seek_arc) + SeekBar seekArc; + @BindView(R.id.timer_display) + TextView timerDisplay; + @BindView(R.id.action_set) + Button setButton; + @BindView(R.id.action_cancel) + Button cancelButton; + + private int seekArcProgress; + private TimerUpdater timerUpdater; + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + timerUpdater.cancel(); + } + + @Override + public void onResume() { + super.onResume(); + if (makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE) != null) { + timerUpdater.start(); + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_sleep_timer, container, false); + ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + timerUpdater = new TimerUpdater(); + + seekArcProgress = PreferenceUtil.getInstance(getActivity()).getLastSleepTimerValue(); + updateTimeDisplayTime(); + seekArc.setProgress(seekArcProgress); + + int accentColor = ThemeStore.accentColor(getContext()); + TintHelper.setTintAuto(seekArc, accentColor, true); + setButton.setTextColor(accentColor); + cancelButton.setTextColor(accentColor); + + seekArc.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + if (i < 1) { + seekArc.setProgress(1); + return; + } + seekArcProgress = i; + updateTimeDisplayTime(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + PreferenceUtil.getInstance(getActivity()).setLastSleepTimerValue(seekArcProgress); + } + }); + } + + @OnClick({R.id.action_cancel, R.id.action_set}) + void set(View view) { + switch (view.getId()) { + case R.id.action_cancel: + if (getActivity() == null) { + return; + } + final PendingIntent previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE); + if (previous != null) { + AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); + if (am != null) { + am.cancel(previous); + } + previous.cancel(); + Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_canceled), Toast.LENGTH_SHORT).show(); + } + break; + case R.id.action_set: + if (getActivity() == null) { + return; + } + final int minutes = seekArcProgress; + PendingIntent pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT); + final long nextSleepTimerElapsedTime = SystemClock.elapsedRealtime() + minutes * 60 * 1000; + PreferenceUtil.getInstance(getActivity()).setNextSleepTimerElapsedRealtime(nextSleepTimerElapsedTime); + AlarmManager am = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE); + if (am != null) { + am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextSleepTimerElapsedTime, pi); + } + Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.sleep_timer_set, minutes), Toast.LENGTH_SHORT).show(); + break; + } + dismiss(); + } + + private void updateTimeDisplayTime() { + timerDisplay.setText(String.format(Locale.getDefault(), "%d min", seekArcProgress)); + } + + private PendingIntent makeTimerPendingIntent(int flag) { + return PendingIntent.getService(getActivity(), 0, makeTimerIntent(), flag); + } + + private Intent makeTimerIntent() { + return new Intent(getActivity(), MusicService.class) + .setAction(ACTION_QUIT); + } + + private class TimerUpdater extends CountDownTimer { + TimerUpdater() { + super(PreferenceUtil.getInstance(getActivity()).getNextSleepTimerElapsedRealTime() - SystemClock.elapsedRealtime(), 1000); + } + + @Override + public void onTick(long millisUntilFinished) { + cancelButton.setText(String.format("%s (%s)", getString(R.string.cancel_current_timer), MusicUtil.getReadableDurationString(millisUntilFinished))); + //materialDialog.setActionButton(DialogAction.NEUTRAL, materialDialog.getContext().getString(R.string.cancel_current_timer) + " (" + MusicUtil.getReadableDurationString(millisUntilFinished) + ")"); + } + + @Override + public void onFinish() { + cancelButton.setText(null); + //materialDialog.setActionButton(DialogAction.NEUTRAL, null); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.java new file mode 100644 index 00000000..a40104d8 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongDetailDialog.java @@ -0,0 +1,117 @@ +package code.name.monkey.retromusic.dialogs; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetDialogFragment; +import android.text.Html; +import android.text.Spanned; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +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; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + +/** + * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) + */ +public class SongDetailDialog extends RoundedBottomSheetDialogFragment { + + public static final String TAG = SongDetailDialog.class.getSimpleName(); + + @NonNull + public static SongDetailDialog create(Song song) { + SongDetailDialog dialog = new SongDetailDialog(); + Bundle args = new Bundle(); + args.putParcelable("song", song); + dialog.setArguments(args); + return dialog; + } + + private static Spanned makeTextWithTitle(@NonNull Context context, int titleResId, String text) { + return Html.fromHtml("" + context.getResources().getString(titleResId) + ": " + "" + text); + } + + private static String getFileSizeString(long sizeInBytes) { + long fileSizeInKB = sizeInBytes / 1024; + long fileSizeInMB = fileSizeInKB / 1024; + return fileSizeInMB + " MB"; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View dialogView = inflater.inflate(R.layout.dialog_file_details, container, false); + Context context = getContext(); + + final TextView fileName = dialogView.findViewById(R.id.file_name); + final TextView filePath = dialogView.findViewById(R.id.file_path); + final TextView fileSize = dialogView.findViewById(R.id.file_size); + final TextView fileFormat = dialogView.findViewById(R.id.file_format); + final TextView trackLength = dialogView.findViewById(R.id.track_length); + final TextView bitRate = dialogView.findViewById(R.id.bitrate); + final TextView samplingRate = 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, "-")); + + final Song song = getArguments().getParcelable("song"); + if (song != null) { + final File songFile = new File(song.data); + if (songFile.exists()) { + 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, + getFileSizeString(songFile.length()))); + try { + AudioFile audioFile = AudioFileIO.read(songFile); + AudioHeader audioHeader = audioFile.getAudioHeader(); + + 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 (@NonNull CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { + Log.e(TAG, "error while reading the song file", e); + // fallback + trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, + MusicUtil.getReadableDurationString(song.duration))); + } + } else { + // fallback + fileName.setText(makeTextWithTitle(context, R.string.label_file_name, song.title)); + trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, + MusicUtil.getReadableDurationString(song.duration))); + } + } + + return dialogView; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.java b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.java new file mode 100644 index 00000000..0454367c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SongShareDialog.java @@ -0,0 +1,71 @@ +package code.name.monkey.retromusic.dialogs; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetDialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment; + + +public class SongShareDialog extends RoundedBottomSheetDialogFragment { + @BindView(R.id.option_2) + TextView currentSong; + + @NonNull + public static SongShareDialog create(final Song song) { + final SongShareDialog dialog = new SongShareDialog(); + final Bundle args = new Bundle(); + args.putParcelable("song", song); + dialog.setArguments(args); + return dialog; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.dialog_file_share, container, false); + ButterKnife.bind(this, layout); + return layout; + } + + @SuppressLint("StringFormatInvalid") + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + final Song song = getArguments().getParcelable("song"); + currentSong.setText(getString(R.string.currently_listening_to_x_by_x, song.title, song.artistName)); + } + + @OnClick({R.id.option_2, R.id.option_1}) + void onClick(View view) { + final Song song = getArguments().getParcelable("song"); + final String currentlyListening = getString(R.string.currently_listening_to_x_by_x, song.title, song.artistName); + switch (view.getId()) { + case R.id.option_1: + startActivity( + Intent.createChooser( + MusicUtil.createShareSongFileIntent(song, getContext()), null)); + break; + case R.id.option_2: + getActivity().startActivity(Intent.createChooser( + new Intent().setAction(Intent.ACTION_SEND) + .putExtra(Intent.EXTRA_TEXT, currentlyListening) + .setType("text/plain"), null)); + break; + } + dismiss(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java new file mode 100644 index 00000000..5835bfcd --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/ArtistGlideRequest.java @@ -0,0 +1,140 @@ +package code.name.monkey.retromusic.glide; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; + +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.DrawableRequestBuilder; +import com.bumptech.glide.DrawableTypeRequest; +import com.bumptech.glide.Priority; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.target.Target; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.glide.artistimage.ArtistImage; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.util.ArtistSignatureUtil; +import code.name.monkey.retromusic.util.CustomArtistImageUtil; + +public class ArtistGlideRequest { + + private static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE; + private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art; + + private static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, + boolean noCustomImage, boolean forceDownload) { + boolean hasCustomImage = CustomArtistImageUtil.getInstance(RetroApplication.getInstance()) + .hasCustomArtistImage(artist); + if (noCustomImage || !hasCustomImage) { + return requestManager.load(new ArtistImage(artist.getName(), forceDownload)); + } else { + return requestManager.load(CustomArtistImageUtil.getFile(artist)); + } + } + + private static Key createSignature(Artist artist) { + return ArtistSignatureUtil.getInstance(RetroApplication.getInstance()) + .getArtistSignature(artist.getName()); + } + + public static class Builder { + + final RequestManager requestManager; + final Artist artist; + boolean noCustomImage; + boolean forceDownload; + + private Builder(@NonNull RequestManager requestManager, Artist artist) { + this.requestManager = requestManager; + this.artist = artist; + } + + public static Builder from(@NonNull RequestManager requestManager, Artist artist) { + return new Builder(requestManager, artist); + } + + public PaletteBuilder generatePalette(Context context) { + return new PaletteBuilder(this, context); + } + + public BitmapBuilder asBitmap() { + return new BitmapBuilder(this); + } + + public Builder noCustomImage(boolean noCustomImage) { + this.noCustomImage = noCustomImage; + return this; + } + + public Builder forceDownload(boolean forceDownload) { + this.forceDownload = forceDownload; + return this; + } + + public DrawableRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(requestManager, artist, noCustomImage, forceDownload) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(artist)); + } + } + + public static class BitmapBuilder { + + private final Builder builder; + + BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, + builder.forceDownload) + .asBitmap() + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(builder.artist)); + } + } + + public static class PaletteBuilder { + + final Context context; + private final Builder builder; + + PaletteBuilder(Builder builder, Context context) { + this.builder = builder; + this.context = context; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, + builder.forceDownload) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(builder.artist)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.java b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.java new file mode 100644 index 00000000..b4659b27 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/BlurTransformation.java @@ -0,0 +1,147 @@ +package code.name.monkey.retromusic.glide; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Build; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RSRuntimeException; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import android.support.annotation.FloatRange; +import android.support.annotation.NonNull; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; +import code.name.monkey.retromusic.helper.StackBlur; + +import code.name.monkey.retromusic.BuildConfig; +import code.name.monkey.retromusic.util.ImageUtil; + + +public class BlurTransformation extends BitmapTransformation { + static final float DEFAULT_BLUR_RADIUS = 5f; + + private Context context; + private float blurRadius; + private int sampling; + + private BlurTransformation(Builder builder) { + super(builder.context); + init(builder); + } + + private BlurTransformation(Builder builder, BitmapPool bitmapPool) { + super(bitmapPool); + init(builder); + } + + private void init(Builder builder) { + this.context = builder.context; + this.blurRadius = builder.blurRadius; + this.sampling = builder.sampling; + } + + @Override + protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { + int sampling; + if (this.sampling == 0) { + sampling = ImageUtil.calculateInSampleSize(toTransform.getWidth(), toTransform.getHeight(), 100); + } else { + sampling = this.sampling; + } + + int width = toTransform.getWidth(); + int height = toTransform.getHeight(); + int scaledWidth = width / sampling; + int scaledHeight = height / sampling; + + Bitmap out = pool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888); + if (out == null) { + out = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(out); + canvas.scale(1 / (float) sampling, 1 / (float) sampling); + Paint paint = new Paint(); + paint.setFlags(Paint.FILTER_BITMAP_FLAG); + canvas.drawBitmap(toTransform, 0, 0, paint); + + if (Build.VERSION.SDK_INT >= 17) { + try { + final RenderScript rs = RenderScript.create(context.getApplicationContext()); + final Allocation input = Allocation.createFromBitmap(rs, out, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + final Allocation output = Allocation.createTyped(rs, input.getType()); + final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + + script.setRadius(blurRadius); + script.setInput(input); + script.forEach(output); + + output.copyTo(out); + + rs.destroy(); + + return out; + + } catch (RSRuntimeException e) { + // on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library + if (BuildConfig.DEBUG) e.printStackTrace(); + } + } + + return StackBlur.blur(out, blurRadius); + } + + @Override + public String getId() { + return "BlurTransformation(radius=" + blurRadius + ", sampling=" + sampling + ")"; + } + + public static class Builder { + private Context context; + private BitmapPool bitmapPool; + private float blurRadius = DEFAULT_BLUR_RADIUS; + private int sampling; + + public Builder(@NonNull Context context) { + this.context = context; + } + + /** + * @param blurRadius The radius to use. Must be between 0 and 25. Default is 5. + * @return the same Builder + */ + public Builder blurRadius(@FloatRange(from = 0.0f, to = 25.0f) float blurRadius) { + this.blurRadius = blurRadius; + return this; + } + + /** + * @param sampling The inSampleSize to use. Must be a power of 2, or 1 for no down sampling or 0 for auto detect sampling. Default is 0. + * @return the same Builder + */ + public Builder sampling(int sampling) { + this.sampling = sampling; + return this; + } + + /** + * @param bitmapPool The BitmapPool to use. + * @return the same Builder + */ + public Builder bitmapPool(BitmapPool bitmapPool) { + this.bitmapPool = bitmapPool; + return this; + } + + public BlurTransformation build() { + if (bitmapPool != null) { + return new BlurTransformation(this, bitmapPool); + } + return new BlurTransformation(this); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.java b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.java new file mode 100644 index 00000000..79fc23aa --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicColoredTarget.java @@ -0,0 +1,53 @@ +package code.name.monkey.retromusic.glide; + +import android.graphics.drawable.Drawable; +import android.widget.ImageView; + +import com.bumptech.glide.request.animation.GlideAnimation; + +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.util.PreferenceUtil; + +import static code.name.monkey.retromusic.util.RetroColorUtil.getColor; +import static code.name.monkey.retromusic.util.RetroColorUtil.getDominantColor; + + +public abstract class RetroMusicColoredTarget extends BitmapPaletteTarget { + + public RetroMusicColoredTarget(ImageView view) { + super(view); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + onColorReady(getDefaultFooterColor()); + } + + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + super.onResourceReady(resource, glideAnimation); + int defaultColor = getDefaultFooterColor(); + + int primaryColor = getColor(resource.getPalette(), defaultColor); + int dominantColor = getDominantColor(resource.getBitmap(), defaultColor); + + onColorReady(PreferenceUtil.getInstance(RetroApplication.getInstance()).isDominantColor() ? + dominantColor : primaryColor); + } + + protected int getDefaultFooterColor() { + return ATHUtil.resolveColor(getView().getContext(), R.attr.defaultFooterColor); + } + + protected int getAlbumArtistFooterColor() { + return ATHUtil.resolveColor(getView().getContext(), R.attr.cardBackgroundColor); + } + + public abstract void onColorReady(int color); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.java b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.java new file mode 100644 index 00000000..ae2074b1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/RetroMusicGlideModule.java @@ -0,0 +1,27 @@ +package code.name.monkey.retromusic.glide; + +import android.content.Context; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.module.GlideModule; +import code.name.monkey.retromusic.glide.artistimage.ArtistImage; +import code.name.monkey.retromusic.glide.artistimage.ArtistImageLoader; +import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; +import code.name.monkey.retromusic.glide.audiocover.AudioFileCoverLoader; + +import java.io.InputStream; + + +public class RetroMusicGlideModule implements GlideModule { + @Override + public void applyOptions(Context context, GlideBuilder builder) { + + } + + @Override + public void registerComponents(Context context, Glide glide) { + glide.register(AudioFileCover.class, InputStream.class, new AudioFileCoverLoader.Factory()); + glide.register(ArtistImage.class, InputStream.class, new ArtistImageLoader.Factory(context)); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java b/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java new file mode 100644 index 00000000..24eb8e63 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/SongGlideRequest.java @@ -0,0 +1,124 @@ +package code.name.monkey.retromusic.glide; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.DrawableRequestBuilder; +import com.bumptech.glide.DrawableTypeRequest; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.signature.MediaStoreSignature; + + +public class SongGlideRequest { + + static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE; + static final int DEFAULT_ANIMATION = android.R.anim.fade_in; + static final int DEFAULT_ERROR_IMAGE = R.drawable.default_album_art; + + static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Song song, + boolean ignoreMediaStore) { + if (ignoreMediaStore) { + return requestManager.load(new AudioFileCover(song.data)); + } else { + return requestManager.loadFromMediaStore(MusicUtil.getMediaStoreAlbumCoverUri(song.albumId)); + } + } + + static Key createSignature(Song song) { + return new MediaStoreSignature("", song.dateModified, 0); + } + + public static class Builder { + + final RequestManager requestManager; + final Song song; + boolean ignoreMediaStore; + + private Builder(@NonNull RequestManager requestManager, Song song) { + this.requestManager = requestManager; + this.song = song; + } + + public static Builder from(@NonNull RequestManager requestManager, Song song) { + return new Builder(requestManager, song); + } + + public PaletteBuilder generatePalette(Context context) { + return new PaletteBuilder(this, context); + } + + public BitmapBuilder asBitmap() { + return new BitmapBuilder(this); + } + + public Builder checkIgnoreMediaStore(Context context) { + return ignoreMediaStore(PreferenceUtil.getInstance(context).ignoreMediaStoreArtwork()); + } + + Builder ignoreMediaStore(boolean ignoreMediaStore) { + this.ignoreMediaStore = ignoreMediaStore; + return this; + } + + public DrawableRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(requestManager, song, ignoreMediaStore) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(song)); + } + } + + public static class BitmapBuilder { + + private final Builder builder; + + BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) + .asBitmap() + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(builder.song)); + } + } + + public static class PaletteBuilder { + + final Context context; + private final Builder builder; + + PaletteBuilder(Builder builder, Context context) { + this.builder = builder; + this.context = context; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .signature(createSignature(builder.song)); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.java b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.java new file mode 100644 index 00000000..ad858466 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImage.java @@ -0,0 +1,12 @@ +package code.name.monkey.retromusic.glide.artistimage; + + +public class ArtistImage { + public final String artistName; + public final boolean skipOkHttpCache; + + public ArtistImage(String artistName, boolean skipOkHttpCache) { + this.artistName = artistName; + this.skipOkHttpCache = skipOkHttpCache; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.java b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.java new file mode 100644 index 00000000..5e65535d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageFetcher.java @@ -0,0 +1,83 @@ +package code.name.monkey.retromusic.glide.artistimage; + +import android.content.Context; + +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.ModelLoader; +import code.name.monkey.retromusic.rest.LastFMRestClient; +import code.name.monkey.retromusic.rest.model.LastFmArtist; + +import code.name.monkey.retromusic.util.LastFMUtil; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +import java.io.IOException; +import java.io.InputStream; + +import retrofit2.Response; + + +public class ArtistImageFetcher implements DataFetcher { + public static final String TAG = ArtistImageFetcher.class.getSimpleName(); + private final LastFMRestClient lastFMRestClient; + private final ArtistImage model; + private final int width; + private final int height; + private Context context; + private ModelLoader urlLoader; + private volatile boolean isCancelled; + private DataFetcher urlFetcher; + + public ArtistImageFetcher(Context context, LastFMRestClient lastFMRestClient, ArtistImage model, ModelLoader urlLoader, int width, int height) { + this.context = context; + this.lastFMRestClient = lastFMRestClient; + this.model = model; + this.urlLoader = urlLoader; + this.width = width; + this.height = height; + } + + @Override + public String getId() { + // makes sure we never ever return null here + return String.valueOf(model.artistName); + } + + @Override + public InputStream loadData(Priority priority) throws Exception { + if (!MusicUtil.isArtistNameUnknown(model.artistName) && RetroUtil.isAllowedToDownloadMetadata(context)) { + Response response = lastFMRestClient.getApiService().getArtistInfo(model.artistName, null, model.skipOkHttpCache ? "no-cache" : null).execute(); + + if (!response.isSuccessful()) { + throw new IOException("Request failed with code: " + response.code()); + } + + LastFmArtist lastFmArtist = response.body(); + + if (isCancelled) return null; + + GlideUrl url = new GlideUrl(LastFMUtil.getLargestArtistImageUrl(lastFmArtist.getArtist().getImage())); + urlFetcher = urlLoader.getResourceFetcher(url, width, height); + + return urlFetcher.loadData(priority); + } + return null; + } + + @Override + public void cleanup() { + if (urlFetcher != null) { + urlFetcher.cleanup(); + } + } + + @Override + public void cancel() { + isCancelled = true; + if (urlFetcher != null) { + urlFetcher.cancel(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.java b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.java new file mode 100644 index 00000000..09392ced --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/artistimage/ArtistImageLoader.java @@ -0,0 +1,68 @@ +package code.name.monkey.retromusic.glide.artistimage; + +import android.content.Context; + +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.model.GenericLoaderFactory; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.stream.StreamModelLoader; +import code.name.monkey.retromusic.rest.LastFMRestClient; + +import java.io.InputStream; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; + + + +public class ArtistImageLoader implements StreamModelLoader { + // we need these very low values to make sure our artist image loading calls doesn't block the image loading queue + private static final int TIMEOUT = 500; + + private Context context; + private LastFMRestClient lastFMClient; + private ModelLoader urlLoader; + + public ArtistImageLoader(Context context, LastFMRestClient lastFMRestClient, ModelLoader urlLoader) { + this.context = context; + this.lastFMClient = lastFMRestClient; + this.urlLoader = urlLoader; + } + + @Override + public DataFetcher getResourceFetcher(ArtistImage model, int width, int height) { + return new ArtistImageFetcher(context, lastFMClient, model, urlLoader, width, height); + } + + public static class Factory implements ModelLoaderFactory { + private LastFMRestClient lastFMClient; + private OkHttpUrlLoader.Factory okHttpFactory; + + public Factory(Context context) { + okHttpFactory = new OkHttpUrlLoader.Factory(new OkHttpClient.Builder() + .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .build()); + lastFMClient = new LastFMRestClient(LastFMRestClient.createDefaultOkHttpClientBuilder(context) + .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) + .build()); + } + + @Override + public ModelLoader build(Context context, GenericLoaderFactory factories) { + return new ArtistImageLoader(context, lastFMClient, okHttpFactory.build(context, factories)); + } + + @Override + public void teardown() { + okHttpFactory.teardown(); + } + } +} + diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java new file mode 100644 index 00000000..3a163bc1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCover.java @@ -0,0 +1,10 @@ +package code.name.monkey.retromusic.glide.audiocover; + + +public class AudioFileCover { + public final String filePath; + + public AudioFileCover(String filePath) { + this.filePath = filePath; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java new file mode 100644 index 00000000..8985da7c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverFetcher.java @@ -0,0 +1,95 @@ +package code.name.monkey.retromusic.glide.audiocover; + +import android.media.MediaMetadataRetriever; + +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.data.DataFetcher; + +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; +import org.jaudiotagger.audio.mp3.MP3File; +import org.jaudiotagger.tag.TagException; +import org.jaudiotagger.tag.images.Artwork; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + + +public class AudioFileCoverFetcher implements DataFetcher { + private static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"}; + private final AudioFileCover model; + private FileInputStream stream; + + public AudioFileCoverFetcher(AudioFileCover model) { + this.model = model; + } + + @Override + public String getId() { + // makes sure we never ever return null here + return String.valueOf(model.filePath); + } + + @Override + public InputStream loadData(Priority priority) throws Exception { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setDataSource(model.filePath); + byte[] picture = retriever.getEmbeddedPicture(); + if (picture != null) { + return new ByteArrayInputStream(picture); + } else { + return fallback(model.filePath); + } + } finally { + retriever.release(); + } + } + + private InputStream fallback(String path) throws FileNotFoundException { + // Method 1: use embedded high resolution album art if there is any + try { + MP3File mp3File = new MP3File(path); + if (mp3File.hasID3v2Tag()) { + Artwork art = mp3File.getTag().getFirstArtwork(); + if (art != null) { + byte[] imageData = art.getBinaryData(); + return new ByteArrayInputStream(imageData); + } + } + // If there are any exceptions, we ignore them and continue to the other fallback method + } catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException ignored) { + } + + // Method 2: look for album art in external files + File parent = new File(path).getParentFile(); + for (String fallback : FALLBACKS) { + File cover = new File(parent, fallback); + if (cover.exists()) { + return stream = new FileInputStream(cover); + } + } + return null; + } + + @Override + public void cleanup() { + // already cleaned up in loadData and ByteArrayInputStream will be GC'd + if (stream != null) { + try { + stream.close(); + } catch (IOException ignore) { + // can't do much about it + } + } + } + + @Override + public void cancel() { + // cannot cancel + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java new file mode 100644 index 00000000..2b02c9b3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/audiocover/AudioFileCoverLoader.java @@ -0,0 +1,32 @@ +package code.name.monkey.retromusic.glide.audiocover; + +import android.content.Context; + +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.model.GenericLoaderFactory; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.stream.StreamModelLoader; + +import java.io.InputStream; + + +public class AudioFileCoverLoader implements StreamModelLoader { + + @Override + public DataFetcher getResourceFetcher(AudioFileCover model, int width, int height) { + return new AudioFileCoverFetcher(model); + } + + public static class Factory implements ModelLoaderFactory { + @Override + public ModelLoader build(Context context, GenericLoaderFactory factories) { + return new AudioFileCoverLoader(); + } + + @Override + public void teardown() { + } + } +} + diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java new file mode 100644 index 00000000..201e43b7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteResource.java @@ -0,0 +1,34 @@ +package code.name.monkey.retromusic.glide.palette; + +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.util.Util; + + +public class BitmapPaletteResource implements Resource { + + private final BitmapPaletteWrapper bitmapPaletteWrapper; + private final BitmapPool bitmapPool; + + public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper, BitmapPool bitmapPool) { + this.bitmapPaletteWrapper = bitmapPaletteWrapper; + this.bitmapPool = bitmapPool; + } + + @Override + public BitmapPaletteWrapper get() { + return bitmapPaletteWrapper; + } + + @Override + public int getSize() { + return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap()); + } + + @Override + public void recycle() { + if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) { + bitmapPaletteWrapper.getBitmap().recycle(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java new file mode 100644 index 00000000..662db217 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTarget.java @@ -0,0 +1,17 @@ +package code.name.monkey.retromusic.glide.palette; + +import android.widget.ImageView; +import com.bumptech.glide.request.target.ImageViewTarget; + +public class BitmapPaletteTarget extends ImageViewTarget { + + public BitmapPaletteTarget(ImageView view) { + super(view); + } + + @Override + protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) { + view.setImageBitmap(bitmapPaletteWrapper.getBitmap()); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java new file mode 100644 index 00000000..ad5cf47a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteTranscoder.java @@ -0,0 +1,34 @@ +package code.name.monkey.retromusic.glide.palette; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; +import code.name.monkey.retromusic.util.RetroColorUtil; + +public class BitmapPaletteTranscoder implements ResourceTranscoder { + private final BitmapPool bitmapPool; + + public BitmapPaletteTranscoder(Context context) { + this(Glide.get(context).getBitmapPool()); + } + + public BitmapPaletteTranscoder(BitmapPool bitmapPool) { + this.bitmapPool = bitmapPool; + } + + @Override + public Resource transcode(Resource bitmapResource) { + Bitmap bitmap = bitmapResource.get(); + BitmapPaletteWrapper bitmapPaletteWrapper = new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap)); + return new BitmapPaletteResource(bitmapPaletteWrapper, bitmapPool); + } + + @Override + public String getId() { + return "BitmapPaletteTranscoder.code.name.monkey.retromusic.glide.palette"; + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java new file mode 100644 index 00000000..e7c53bea --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/glide/palette/BitmapPaletteWrapper.java @@ -0,0 +1,22 @@ +package code.name.monkey.retromusic.glide.palette; + +import android.graphics.Bitmap; +import android.support.v7.graphics.Palette; + +public class BitmapPaletteWrapper { + private final Bitmap mBitmap; + private final Palette mPalette; + + public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) { + mBitmap = bitmap; + mPalette = palette; + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public Palette getPalette() { + return mPalette; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.java new file mode 100644 index 00000000..12f98ba4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/EqualizerHelper.java @@ -0,0 +1,171 @@ +package code.name.monkey.retromusic.helper; + +import android.media.audiofx.BassBoost; +import android.media.audiofx.Equalizer; +import android.media.audiofx.Virtualizer; +import android.util.Log; + +import code.name.monkey.retromusic.interfaces.EqualizerInterface; + +/** + * @author Hemanth S (h4h13). + */ + +public class EqualizerHelper implements EqualizerInterface { + private static final String TAG = "EqualizerHelper"; + private static volatile EqualizerHelper ourInstance; + private Equalizer mEqualizer; + private BassBoost mBassBoost; + private Virtualizer mVirtualizer; + + private int mMaxLevel, mMinLevel; + private boolean isRunning = false; + + private EqualizerHelper() { + + //Prevent form the reflection api. + if (ourInstance != null) { + throw new RuntimeException("Use getInstance() method to get the single instance of this class."); + } + + int i = MusicPlayerRemote.getAudioSessionId(); + + mEqualizer = new Equalizer(100, i); + if (mEqualizer == null) { + Log.i(TAG, "onCreate: Equalizer is null"); + return; + } + mEqualizer.setEnabled(true); + + + mBassBoost = new BassBoost(100, i); + if (mBassBoost == null) { + Log.i(TAG, "onCreate: BassBoost is null"); + return; + } + + mVirtualizer = new Virtualizer(100, i); + if (mVirtualizer == null) { + Log.i(TAG, "onCreate: Virtualizer is null"); + return; + } + + mMaxLevel = (int) mEqualizer.getBandLevelRange()[1]; + mMinLevel = (int) mEqualizer.getBandLevelRange()[0]; + + Log.i(TAG, "onCreate: " + mMaxLevel + " " + mMinLevel); + isRunning = true; + } + + public static EqualizerHelper getInstance() { + //Double check locking pattern + if (ourInstance == null) {//Check for the first time + + synchronized (EqualizerHelper.class) {//Check for the second time. + + //if there is no instance available... create new one + if (ourInstance == null) { + ourInstance = new EqualizerHelper(); + } + } + } + return ourInstance; + } + + //Make singleton from serialize and deserialize operation. + protected EqualizerHelper readResolve() { + return getInstance(); + } + + @Override + public Equalizer getEqualizer() { + return mEqualizer; + } + + @Override + public BassBoost getBassBoost() { + return mBassBoost; + } + + @Override + public Virtualizer getVirtualizer() { + return mVirtualizer; + } + + @Override + public int getBandLevelLow() { + return mMinLevel; + } + + @Override + public int getBandLevelHigh() { + return mMaxLevel; + } + + @Override + public int getNumberOfBands() { + return (int) mEqualizer.getNumberOfBands(); + } + + @Override + public int getCenterFreq(int band) { + return (int) mEqualizer.getCenterFreq((short) band); + } + + + @Override + public int getBandLevel(int band) { + return (int) mEqualizer.getBandLevel((short) band); + } + + @Override + public void setBandLevel(int band, int level) { + mEqualizer.setBandLevel((short) band, (short) level); + } + + @Override + public boolean isBassBoostEnabled() { + return mBassBoost.getEnabled(); + } + + @Override + public void setBassBoostEnabled(boolean isEnabled) { + mBassBoost.setEnabled(isEnabled); + } + + @Override + public int getBassBoostStrength() { + return (int) mBassBoost.getRoundedStrength(); + } + + @Override + public void setBassBoostStrength(int strength) { + mBassBoost.setStrength((short) strength); + } + + @Override + public boolean isVirtualizerEnabled() { + return mVirtualizer.getEnabled(); + } + + @Override + public void setVirtualizerEnabled(boolean isEnabled) { + mVirtualizer.setEnabled(isEnabled); + } + + @Override + public int getVirtualizerStrength() { + return mVirtualizer.getRoundedStrength(); + } + + @Override + public void setVirtualizerStrength(int strength) { + mVirtualizer.setStrength((short) strength); + } + + @Override + public boolean isRunning() { + return isRunning; + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.java new file mode 100644 index 00000000..fd1ae91a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/HorizontalAdapterHelper.java @@ -0,0 +1,36 @@ +package code.name.monkey.retromusic.helper; + +import android.content.Context; +import android.view.ViewGroup; +import code.name.monkey.retromusic.R; + + +public class HorizontalAdapterHelper { + + public static final int LAYOUT_RES = R.layout.item_image; + + public static final int TYPE_FIRST = 1; + public static final int TYPE_MIDDLE = 2; + public static final int TYPE_LAST = 3; + + public static void applyMarginToLayoutParams(Context context, + ViewGroup.MarginLayoutParams layoutParams, int viewType) { + int listMargin = context.getResources() + .getDimensionPixelSize(R.dimen.now_playing_top_margin); + if (viewType == TYPE_FIRST) { + layoutParams.leftMargin = listMargin; + } else if (viewType == TYPE_LAST) { + layoutParams.rightMargin = listMargin; + } + } + + public static int getItemViewtype(int position, int itemCount) { + if (position == 0) { + return TYPE_FIRST; + } else if (position == itemCount - 1) { + return TYPE_LAST; + } else { + return TYPE_MIDDLE; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java b/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java new file mode 100644 index 00000000..e6ffdf39 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UConstants.java @@ -0,0 +1,8 @@ +package code.name.monkey.retromusic.helper; + +public interface M3UConstants { + String EXTENSION = "m3u"; + String HEADER = "#EXTM3U"; + String ENTRY = "#EXTINF:"; + String DURATION_SEPARATOR = ","; +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java new file mode 100644 index 00000000..5e9416eb --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.java @@ -0,0 +1,58 @@ +package code.name.monkey.retromusic.helper; + +import android.content.Context; +import android.support.annotation.NonNull; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; +import code.name.monkey.retromusic.model.AbsCustomPlaylist; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import io.reactivex.Observable; +import io.reactivex.ObservableEmitter; + +public class M3UWriter implements M3UConstants { + public static final String TAG = M3UWriter.class.getSimpleName(); + + public static Observable write(@NonNull Context context, + @NonNull File dir, @NonNull Playlist playlist) { + if (!dir.exists()) //noinspection ResultOfMethodCallIgnored + dir.mkdirs(); + File file = new File(dir, playlist.name.concat("." + EXTENSION)); + + if (playlist instanceof AbsCustomPlaylist) { + return Observable.create(e -> { + ((AbsCustomPlaylist) playlist).getSongs(context).subscribe(songs -> { + saveSongsToFile(file, e, songs); + }); + }); + } else + return Observable.create(e -> + PlaylistSongsLoader.getPlaylistSongList(context, playlist.id) + .subscribe(songs -> { + saveSongsToFile(file, e, songs); + })); + } + + private static void saveSongsToFile(File file, ObservableEmitter e, ArrayList songs) throws IOException { + if (songs.size() > 0) { + BufferedWriter bw = new BufferedWriter(new FileWriter(file)); + bw.write(HEADER); + for (Song song : songs) { + bw.newLine(); + bw.write(ENTRY + song.duration + DURATION_SEPARATOR + song.artistName + " - " + song.title); + bw.newLine(); + bw.write(song.data); + } + + bw.close(); + } + e.onNext(file); + e.onComplete(); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.java b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.java new file mode 100644 index 00000000..ccd21773 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/MusicPlayerRemote.java @@ -0,0 +1,480 @@ +package code.name.monkey.retromusic.helper; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.ServiceConnection; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.IBinder; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; +import java.util.Random; +import java.util.WeakHashMap; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.schedulers.Schedulers; + + +public class MusicPlayerRemote { + + public static final String TAG = MusicPlayerRemote.class.getSimpleName(); + private static final WeakHashMap mConnectionMap = new WeakHashMap<>(); + @Nullable + public static MusicService musicService; + + public static ServiceToken bindToService(@NonNull final Context context, + final ServiceConnection callback) { + Activity realActivity = ((Activity) context).getParent(); + if (realActivity == null) { + realActivity = (Activity) context; + } + + final ContextWrapper contextWrapper = new ContextWrapper(realActivity); + contextWrapper.startService(new Intent(contextWrapper, MusicService.class)); + + final ServiceBinder binder = new ServiceBinder(callback); + + if (contextWrapper.bindService(new Intent().setClass(contextWrapper, MusicService.class), binder, Context.BIND_AUTO_CREATE)) { + mConnectionMap.put(contextWrapper, binder); + return new ServiceToken(contextWrapper); + } + return null; + } + + public static void unbindFromService(@Nullable final ServiceToken token) { + if (token == null) { + return; + } + final ContextWrapper mContextWrapper = token.mWrappedContext; + final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper); + if (mBinder == null) { + return; + } + mContextWrapper.unbindService(mBinder); + if (mConnectionMap.isEmpty()) { + musicService = null; + } + } + + @Nullable + private static String getFilePathFromUri(Context context, Uri uri) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, null, null, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + /** + * Async + */ + public static void playSongAt(final int position) { + if (musicService != null) { + musicService.playSongAt(position); + } + } + + public static void pauseSong() { + if (musicService != null) { + musicService.pause(); + } + } + + /** + * Async + */ + public static void playNextSong() { + if (musicService != null) { + musicService.playNextSong(true); + } + } + + /** + * Async + */ + public static void playPreviousSong() { + if (musicService != null) { + musicService.playPreviousSong(true); + } + } + + /** + * Async + */ + public static void back() { + if (musicService != null) { + musicService.back(true); + } + } + + public static boolean isPlaying() { + return musicService != null && musicService.isPlaying(); + } + + public static void resumePlaying() { + if (musicService != null) { + musicService.play(); + } + } + + /** + * Async + */ + public static void openQueue(final ArrayList queue, final int startPosition, final boolean startPlaying) { + if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) { + musicService.openQueue(queue, startPosition, startPlaying); + if (PreferenceUtil.getInstance(musicService).isShuffleModeOn()) + setShuffleMode(MusicService.SHUFFLE_MODE_NONE); + } + } + + /** + * Async + */ + public static void openAndShuffleQueue(final ArrayList queue, boolean startPlaying) { + int startPosition = 0; + if (!queue.isEmpty()) { + startPosition = new Random().nextInt(queue.size()); + } + + if (!tryToHandleOpenPlayingQueue(queue, startPosition, startPlaying) && musicService != null) { + openQueue(queue, startPosition, startPlaying); + setShuffleMode(MusicService.SHUFFLE_MODE_SHUFFLE); + } + } + + private static boolean tryToHandleOpenPlayingQueue(final ArrayList queue, final int startPosition, final boolean startPlaying) { + if (getPlayingQueue() == queue) { + if (startPlaying) { + playSongAt(startPosition); + } else { + setPosition(startPosition); + } + return true; + } + return false; + } + + public static Song getCurrentSong() { + if (musicService != null) { + return musicService.getCurrentSong(); + } + return Song.EMPTY_SONG; + } + + public static int getPosition() { + if (musicService != null) { + return musicService.getPosition(); + } + return -1; + } + + /** + * Async + */ + public static void setPosition(final int position) { + if (musicService != null) { + musicService.setPosition(position); + } + } + + public static ArrayList getPlayingQueue() { + if (musicService != null) { + return musicService.getPlayingQueue(); + } + return new ArrayList<>(); + } + + public static int getSongProgressMillis() { + if (musicService != null) { + return musicService.getSongProgressMillis(); + } + return -1; + } + + public static int getSongDurationMillis() { + if (musicService != null) { + return musicService.getSongDurationMillis(); + } + return -1; + } + + public static long getQueueDurationMillis(int position) { + if (musicService != null) { + return musicService.getQueueDurationMillis(position); + } + return -1; + } + + public static int seekTo(int millis) { + if (musicService != null) { + return musicService.seek(millis); + } + return -1; + } + + public static int getRepeatMode() { + if (musicService != null) { + return musicService.getRepeatMode(); + } + return MusicService.REPEAT_MODE_NONE; + } + + public static int getShuffleMode() { + if (musicService != null) { + return musicService.getShuffleMode(); + } + return MusicService.SHUFFLE_MODE_NONE; + } + + public static boolean cycleRepeatMode() { + if (musicService != null) { + musicService.cycleRepeatMode(); + return true; + } + return false; + } + + public static boolean toggleShuffleMode() { + if (musicService != null) { + musicService.toggleShuffle(); + return true; + } + return false; + } + + public static boolean setShuffleMode(final int shuffleMode) { + if (musicService != null) { + musicService.setShuffleMode(shuffleMode); + return true; + } + return false; + } + + public static boolean playNext(Song song) { + if (musicService != null) { + if (getPlayingQueue().size() > 0) { + musicService.addSong(getPosition() + 1, song); + } else { + ArrayList queue = new ArrayList<>(); + queue.add(song); + openQueue(queue, 0, false); + } + Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show(); + return true; + } + return false; + } + + public static boolean playNext(@NonNull ArrayList songs) { + if (musicService != null) { + if (getPlayingQueue().size() > 0) { + musicService.addSongs(getPosition() + 1, songs); + } else { + openQueue(songs, 0, false); + } + final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_playing_queue) : musicService.getResources().getString(R.string.added_x_titles_to_playing_queue, songs.size()); + Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show(); + return true; + } + return false; + } + + public static boolean enqueue(Song song) { + if (musicService != null) { + if (getPlayingQueue().size() > 0) { + musicService.addSong(song); + } else { + ArrayList queue = new ArrayList<>(); + queue.add(song); + openQueue(queue, 0, false); + } + Toast.makeText(musicService, musicService.getResources().getString(R.string.added_title_to_playing_queue), Toast.LENGTH_SHORT).show(); + return true; + } + return false; + } + + public static boolean enqueue(@NonNull ArrayList songs) { + if (musicService != null) { + if (getPlayingQueue().size() > 0) { + musicService.addSongs(songs); + } else { + openQueue(songs, 0, false); + } + final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_playing_queue) : musicService.getResources().getString(R.string.added_x_titles_to_playing_queue, songs.size()); + Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show(); + return true; + } + return false; + } + + public static boolean removeFromQueue(@NonNull Song song) { + if (musicService != null) { + musicService.removeSong(song); + return true; + } + return false; + } + + public static boolean removeFromQueue(int position) { + if (musicService != null && position >= 0 && position < getPlayingQueue().size()) { + musicService.removeSong(position); + return true; + } + return false; + } + + public static boolean moveSong(int from, int to) { + if (musicService != null && from >= 0 && to >= 0 && from < getPlayingQueue().size() && to < getPlayingQueue().size()) { + musicService.moveSong(from, to); + return true; + } + return false; + } + + public static boolean clearQueue() { + if (musicService != null) { + musicService.clearQueue(); + return true; + } + return false; + } + + public static int getAudioSessionId() { + if (musicService != null) { + return musicService.getAudioSessionId(); + } + return -1; + } + + public static void playFromUri(Uri uri) { + if (musicService != null) { + ArrayList songs = null; + if (uri.getScheme() != null && uri.getAuthority() != null) { + if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + String songId = null; + if (uri.getAuthority().equals("com.android.providers.media.documents")) { + songId = getSongIdFromMediaProvider(uri); + } else if (uri.getAuthority().equals("media")) { + songId = uri.getLastPathSegment(); + } + if (songId != null) { + /* songs = SongLoader.getSongs(SongLoader.makeSongCursor( + musicService, + MediaStore.Audio.AudioColumns._ID + "=?", + new String[]{songId} + ));*/ + songs = SongLoader.getSongs(SongLoader.makeSongCursor( + musicService, + MediaStore.Audio.AudioColumns._ID + "=?", + new String[]{songId})) + .subscribeOn(Schedulers.io()).blockingFirst(); + } + } + } + if (songs == null) { + File songFile = null; + if (uri.getAuthority() != null && uri.getAuthority().equals("com.android.externalstorage.documents")) { + songFile = new File(Environment.getExternalStorageDirectory(), uri.getPath().split(":", 2)[1]); + } + if (songFile == null) { + String path = getFilePathFromUri(musicService, uri); + if (path != null) + songFile = new File(path); + } + if (songFile == null && uri.getPath() != null) { + songFile = new File(uri.getPath()); + } + if (songFile != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor( + musicService, + MediaStore.Audio.AudioColumns.DATA + "=?", + new String[]{songFile.getAbsolutePath()} + )).blockingFirst(); + } + } + if (songs != null && !songs.isEmpty()) { + openQueue(songs, 0, true); + } else { + //TODO the file is not listed in the media store + } + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static String getSongIdFromMediaProvider(Uri uri) { + return DocumentsContract.getDocumentId(uri).split(":")[1]; + } + + public static boolean isServiceConnected() { + return musicService != null; + } + + + public static final class ServiceBinder implements ServiceConnection { + private final ServiceConnection mCallback; + + ServiceBinder(final ServiceConnection callback) { + mCallback = callback; + } + + @Override + public void onServiceConnected(final ComponentName className, final IBinder service) { + MusicService.MusicBinder binder = (MusicService.MusicBinder) service; + musicService = binder.getService(); + if (mCallback != null) { + mCallback.onServiceConnected(className, service); + } + } + + @Override + public void onServiceDisconnected(final ComponentName className) { + if (mCallback != null) { + mCallback.onServiceDisconnected(className); + } + musicService = null; + } + } + + public static final class ServiceToken { + ContextWrapper mWrappedContext; + + ServiceToken(final ContextWrapper context) { + mWrappedContext = context; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.java new file mode 100644 index 00000000..f3f886c2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/MusicProgressViewUpdateHelper.java @@ -0,0 +1,71 @@ +package code.name.monkey.retromusic.helper; + +import android.os.Handler; +import android.os.Message; +import android.support.annotation.NonNull; + + +public class MusicProgressViewUpdateHelper extends Handler { + private static final int CMD_REFRESH_PROGRESS_VIEWS = 1; + + private static final int MIN_INTERVAL = 20; + private static final int UPDATE_INTERVAL_PLAYING = 1000; + private static final int UPDATE_INTERVAL_PAUSED = 500; + + private Callback callback; + private int intervalPlaying; + private int intervalPaused; + + public void start() { + queueNextRefresh(1); + } + + public void stop() { + removeMessages(CMD_REFRESH_PROGRESS_VIEWS); + } + + public MusicProgressViewUpdateHelper(Callback callback) { + this.callback = callback; + this.intervalPlaying = UPDATE_INTERVAL_PLAYING; + this.intervalPaused = UPDATE_INTERVAL_PAUSED; + } + + public MusicProgressViewUpdateHelper(Callback callback, int intervalPlaying, int intervalPaused) { + this.callback = callback; + this.intervalPlaying = intervalPlaying; + this.intervalPaused = intervalPaused; + } + + @Override + public void handleMessage(@NonNull Message msg) { + super.handleMessage(msg); + if (msg.what == CMD_REFRESH_PROGRESS_VIEWS) { + queueNextRefresh(refreshProgressViews()); + } + } + + private int refreshProgressViews() { + final int progressMillis = MusicPlayerRemote.getSongProgressMillis(); + final int totalMillis = MusicPlayerRemote.getSongDurationMillis(); + + callback.onUpdateProgressViews(progressMillis, totalMillis); + + if (!MusicPlayerRemote.isPlaying()) { + return intervalPaused; + } + + final int remainingMillis = intervalPlaying - progressMillis % intervalPlaying; + + return Math.max(MIN_INTERVAL, remainingMillis); + } + + private void queueNextRefresh(final long delay) { + final Message message = obtainMessage(CMD_REFRESH_PROGRESS_VIEWS); + removeMessages(CMD_REFRESH_PROGRESS_VIEWS); + sendMessageDelayed(message, delay); + } + + public interface Callback { + void onUpdateProgressViews(int progress, int total); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.java b/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.java new file mode 100644 index 00000000..4d69a5a4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/PlayPauseButtonOnClickHandler.java @@ -0,0 +1,15 @@ +package code.name.monkey.retromusic.helper; + +import android.view.View; + + +public class PlayPauseButtonOnClickHandler implements View.OnClickListener { + @Override + public void onClick(View v) { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.java new file mode 100644 index 00000000..fb8c99e1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SearchQueryHelper.java @@ -0,0 +1,91 @@ +package code.name.monkey.retromusic.helper; + +import android.app.SearchManager; +import android.content.Context; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Song; + + +public class SearchQueryHelper { + private static final String TITLE_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.TITLE + ") = ?"; + private static final String ALBUM_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ALBUM + ") = ?"; + private static final String ARTIST_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ARTIST + ") = ?"; + private static final String AND = " AND "; + private static ArrayList songs = new ArrayList<>(); + + public static ArrayList getSongs() { + return songs; + } + + public static void setSongs(ArrayList songs) { + SearchQueryHelper.songs = songs; + } + + @NonNull + public static ArrayList getSongs(@NonNull final Context context, @NonNull final Bundle extras) { + final String query = extras.getString(SearchManager.QUERY, null); + final String artistName = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST, null); + final String albumName = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM, null); + final String titleName = extras.getString(MediaStore.EXTRA_MEDIA_TITLE, null); + + ArrayList songs = new ArrayList<>(); + if (artistName != null && albumName != null && titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION, new String[]{artistName.toLowerCase(), albumName.toLowerCase(), titleName.toLowerCase()})).blockingFirst(); + } + if (!songs.isEmpty()) { + return songs; + } + if (artistName != null && titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + TITLE_SELECTION, new String[]{artistName.toLowerCase(), titleName.toLowerCase()})).blockingFirst(); + } + if (!songs.isEmpty()) { + return songs; + } + if (albumName != null && titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION + AND + TITLE_SELECTION, new String[]{albumName.toLowerCase(), titleName.toLowerCase()})).blockingFirst(); + } + if (!songs.isEmpty()) { + return songs; + } + if (artistName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, new String[]{artistName.toLowerCase()})).blockingFirst(); + } + if (!songs.isEmpty()) { + return songs; + } + if (albumName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, new String[]{albumName.toLowerCase()})).blockingFirst(); + } + if (!songs.isEmpty()) { + return songs; + } + if (titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, new String[]{titleName.toLowerCase()})).blockingFirst(); + } + if (!songs.isEmpty()) { + return songs; + } + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, new String[]{query.toLowerCase()})).blockingFirst(); + + if (!songs.isEmpty()) { + return songs; + } + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, new String[]{query.toLowerCase()})).blockingFirst(); + if (!songs.isEmpty()) { + return songs; + } + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, new String[]{query.toLowerCase()})).blockingFirst(); + if (!songs.isEmpty()) { + return songs; + } + return new ArrayList(); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.java new file mode 100644 index 00000000..5e7b4182 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/ShuffleHelper.java @@ -0,0 +1,25 @@ +package code.name.monkey.retromusic.helper; + +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.model.Song; + +import java.util.Collections; +import java.util.List; + + +public class ShuffleHelper { + + public static void makeShuffleList(@NonNull List listToShuffle, final int current) { + if (listToShuffle.isEmpty()) return; + if (current >= 0) { + Song song = listToShuffle.remove(current); + Collections.shuffle(listToShuffle); + listToShuffle.add(0, song); + } else { + Collections.shuffle(listToShuffle); + } + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.java b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.java new file mode 100644 index 00000000..da99cfc4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/SortOrder.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2012 Andrew Neal 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 code.name.monkey.retromusic.helper; + +import android.provider.MediaStore; + +/** + * Holds all of the sort orders for each list type. + * + * @author Andrew Neal (andrewdneal@gmail.com) + */ +public final class SortOrder { + + /** + * This class is never instantiated + */ + public SortOrder() { + } + + /** + * Artist sort order entries. + */ + public interface ArtistSortOrder { + + /* Artist sort order A-Z */ + String ARTIST_A_Z = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER; + + /* Artist sort order Z-A */ + String ARTIST_Z_A = ARTIST_A_Z + " DESC"; + + /* Artist sort order number of songs */ + String ARTIST_NUMBER_OF_SONGS = MediaStore.Audio.Artists.NUMBER_OF_TRACKS + + " DESC"; + + /* Artist sort order number of albums */ + String ARTIST_NUMBER_OF_ALBUMS = MediaStore.Audio.Artists.NUMBER_OF_ALBUMS + + " DESC"; + } + + /** + * Album sort order entries. + */ + public interface AlbumSortOrder { + + /* Album sort order A-Z */ + String ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER; + + /* Album sort order Z-A */ + String ALBUM_Z_A = ALBUM_A_Z + " DESC"; + + /* Album sort order songs */ + String ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.Albums.NUMBER_OF_SONGS + + " DESC"; + + /* Album sort order artist */ + String ALBUM_ARTIST = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER + + ", " + MediaStore.Audio.Albums.DEFAULT_SORT_ORDER; + + /* Album sort order year */ + String ALBUM_YEAR = MediaStore.Audio.Media.YEAR + " DESC"; + } + + /** + * Song sort order entries. + */ + public interface SongSortOrder { + + /* Song sort order A-Z */ + String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER; + + /* Song sort order Z-A */ + String SONG_Z_A = SONG_A_Z + " DESC"; + + /* Song sort order artist */ + String SONG_ARTIST = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER; + + /* Song sort order album */ + String SONG_ALBUM = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER; + + /* Song sort order year */ + String SONG_YEAR = MediaStore.Audio.Media.YEAR + " DESC"; + + /* Song sort order duration */ + String SONG_DURATION = MediaStore.Audio.Media.DURATION + " DESC"; + + /* Song sort order date */ + String SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + " DESC"; + } + + /** + * Album song sort order entries. + */ + public interface AlbumSongSortOrder { + + /* Album song sort order A-Z */ + String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER; + + /* Album song sort order Z-A */ + String SONG_Z_A = SONG_A_Z + " DESC"; + + /* Album song sort order track list */ + String SONG_TRACK_LIST = MediaStore.Audio.Media.TRACK + ", " + + MediaStore.Audio.Media.DEFAULT_SORT_ORDER; + + /* Album song sort order duration */ + String SONG_DURATION = SongSortOrder.SONG_DURATION; + } + + /** + * Artist song sort order entries. + */ + public interface ArtistSongSortOrder { + + /* Artist song sort order A-Z */ + String SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER; + + /* Artist song sort order Z-A */ + String SONG_Z_A = SONG_A_Z + " DESC"; + + /* Artist song sort order album */ + String SONG_ALBUM = MediaStore.Audio.Media.ALBUM; + + /* Artist song sort order year */ + String SONG_YEAR = MediaStore.Audio.Media.YEAR + " DESC"; + + /* Artist song sort order duration */ + String SONG_DURATION = MediaStore.Audio.Media.DURATION + " DESC"; + + /* Artist song sort order date */ + String SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + " DESC"; + } + + /** + * Artist album sort order entries. + */ + public interface ArtistAlbumSortOrder { + + /* Artist album sort order A-Z */ + String ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER; + + /* Artist album sort order Z-A */ + String ALBUM_Z_A = ALBUM_A_Z + " DESC"; + + /* Artist album sort order year */ + String ALBUM_YEAR = MediaStore.Audio.Media.YEAR + + " DESC"; + + /* Artist album sort order year */ + String ALBUM_YEAR_ASC = MediaStore.Audio.Media.YEAR + + " ASC"; + } + + /** + * Genre sort order entries. + */ + public interface GenreSortOrder { + + /* Genre sort order A-Z */ + String GENRE_A_Z = MediaStore.Audio.Genres.DEFAULT_SORT_ORDER; + + /* Genre sort order Z-A */ + String ALBUM_Z_A = GENRE_A_Z + " DESC"; + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java new file mode 100644 index 00000000..3e812e8a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/StackBlur.java @@ -0,0 +1,333 @@ +package code.name.monkey.retromusic.helper; + +import android.graphics.Bitmap; + +import java.util.ArrayList; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Blur using Java code. + *

+ * This is a compromise between Gaussian Blur and Box blur + * It creates much better looking blurs than Box Blur, but is + * 7x faster than my Gaussian Blur implementation. + *

+ * I called it Stack Blur because this describes best how this + * filter works internally: it creates a kind of moving stack + * of colors whilst scanning through the image. Thereby it + * just has to add one new block of color to the right side + * of the stack and remove the leftmost color. The remaining + * colors on the topmost layer of the stack are either added on + * or reduced by one, depending on if they are on the right or + * on the left side of the stack. + * + * @author Enrique López Mañas + * http://www.neo-tech.es + *

+ * Author of the original algorithm: Mario Klingemann + *

+ * Based heavily on http://vitiy.info/Code/stackblur.cpp + * See http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp/ + * @copyright: Enrique López Mañas + * @license: Apache License 2.0 + */ +public class StackBlur { + + static final int EXECUTOR_THREADS = Runtime.getRuntime().availableProcessors(); + static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(EXECUTOR_THREADS); + + private static final short[] stackblur_mul = { + 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, + 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, + 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, + 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, + 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, + 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, + 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, + 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, + 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, + 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, + 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, + 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, + 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, + 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, + 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, + 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259 + }; + + private static final byte[] stackblur_shr = { + 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 + }; + + public static Bitmap blur(Bitmap original, float radius) { + int w = original.getWidth(); + int h = original.getHeight(); + int[] currentPixels = new int[w * h]; + original.getPixels(currentPixels, 0, w, 0, 0, w, h); + int cores = EXECUTOR_THREADS; + + ArrayList horizontal = new ArrayList(cores); + ArrayList vertical = new ArrayList(cores); + for (int i = 0; i < cores; i++) { + horizontal.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 1)); + vertical.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 2)); + } + + try { + EXECUTOR.invokeAll(horizontal); + } catch (InterruptedException e) { + return null; + } + + try { + EXECUTOR.invokeAll(vertical); + } catch (InterruptedException e) { + return null; + } + + return Bitmap.createBitmap(currentPixels, w, h, Bitmap.Config.ARGB_8888); + } + + private static void blurIteration(int[] src, int w, int h, int radius, int cores, int core, int step) { + int x, y, xp, yp, i; + int sp; + int stack_start; + int stack_i; + + int src_i; + int dst_i; + + long sum_r, sum_g, sum_b, + sum_in_r, sum_in_g, sum_in_b, + sum_out_r, sum_out_g, sum_out_b; + + int wm = w - 1; + int hm = h - 1; + int div = (radius * 2) + 1; + int mul_sum = stackblur_mul[radius]; + byte shr_sum = stackblur_shr[radius]; + int[] stack = new int[div]; + + if (step == 1) { + int minY = core * h / cores; + int maxY = (core + 1) * h / cores; + + for (y = minY; y < maxY; y++) { + sum_r = sum_g = sum_b = + sum_in_r = sum_in_g = sum_in_b = + sum_out_r = sum_out_g = sum_out_b = 0; + + src_i = w * y; // start of line (0,y) + + for (i = 0; i <= radius; i++) { + stack_i = i; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1); + sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1); + sum_b += (src[src_i] & 0xff) * (i + 1); + sum_out_r += ((src[src_i] >>> 16) & 0xff); + sum_out_g += ((src[src_i] >>> 8) & 0xff); + sum_out_b += (src[src_i] & 0xff); + } + + + for (i = 1; i <= radius; i++) { + if (i <= wm) src_i += 1; + stack_i = i + radius; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i); + sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i); + sum_b += (src[src_i] & 0xff) * (radius + 1 - i); + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + } + + + sp = radius; + xp = radius; + if (xp > wm) xp = wm; + src_i = xp + y * w; // img.pix_ptr(xp, y); + dst_i = y * w; // img.pix_ptr(0, y); + for (x = 0; x < w; x++) { + src[dst_i] = (int) + ((src[dst_i] & 0xFFFFFFFF) | + ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) | + ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) | + ((((sum_b * mul_sum) >>> shr_sum) & 0xff))); + dst_i += 1; + + sum_r -= sum_out_r; + sum_g -= sum_out_g; + sum_b -= sum_out_b; + + stack_start = sp + div - radius; + if (stack_start >= div) stack_start -= div; + stack_i = stack_start; + + sum_out_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_out_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_out_b -= (stack[stack_i] & 0xff); + + if (xp < wm) { + src_i += 1; + ++xp; + } + + stack[stack_i] = src[src_i]; + + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + sum_r += sum_in_r; + sum_g += sum_in_g; + sum_b += sum_in_b; + + ++sp; + if (sp >= div) sp = 0; + stack_i = sp; + + sum_out_r += ((stack[stack_i] >>> 16) & 0xff); + sum_out_g += ((stack[stack_i] >>> 8) & 0xff); + sum_out_b += (stack[stack_i] & 0xff); + sum_in_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_in_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_in_b -= (stack[stack_i] & 0xff); + } + + } + } + + // step 2 + else if (step == 2) { + int minX = core * w / cores; + int maxX = (core + 1) * w / cores; + + for (x = minX; x < maxX; x++) { + sum_r = sum_g = sum_b = + sum_in_r = sum_in_g = sum_in_b = + sum_out_r = sum_out_g = sum_out_b = 0; + + src_i = x; // x,0 + for (i = 0; i <= radius; i++) { + stack_i = i; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1); + sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1); + sum_b += (src[src_i] & 0xff) * (i + 1); + sum_out_r += ((src[src_i] >>> 16) & 0xff); + sum_out_g += ((src[src_i] >>> 8) & 0xff); + sum_out_b += (src[src_i] & 0xff); + } + for (i = 1; i <= radius; i++) { + if (i <= hm) src_i += w; // +stride + + stack_i = i + radius; + stack[stack_i] = src[src_i]; + sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i); + sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i); + sum_b += (src[src_i] & 0xff) * (radius + 1 - i); + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + } + + sp = radius; + yp = radius; + if (yp > hm) yp = hm; + src_i = x + yp * w; // img.pix_ptr(x, yp); + dst_i = x; // img.pix_ptr(x, 0); + for (y = 0; y < h; y++) { + src[dst_i] = (int) + ((src[dst_i] & 0xFFFFFFFF) | + ((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) | + ((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) | + ((((sum_b * mul_sum) >>> shr_sum) & 0xff))); + dst_i += w; + + sum_r -= sum_out_r; + sum_g -= sum_out_g; + sum_b -= sum_out_b; + + stack_start = sp + div - radius; + if (stack_start >= div) stack_start -= div; + stack_i = stack_start; + + sum_out_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_out_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_out_b -= (stack[stack_i] & 0xff); + + if (yp < hm) { + src_i += w; // stride + ++yp; + } + + stack[stack_i] = src[src_i]; + + sum_in_r += ((src[src_i] >>> 16) & 0xff); + sum_in_g += ((src[src_i] >>> 8) & 0xff); + sum_in_b += (src[src_i] & 0xff); + sum_r += sum_in_r; + sum_g += sum_in_g; + sum_b += sum_in_b; + + ++sp; + if (sp >= div) sp = 0; + stack_i = sp; + + sum_out_r += ((stack[stack_i] >>> 16) & 0xff); + sum_out_g += ((stack[stack_i] >>> 8) & 0xff); + sum_out_b += (stack[stack_i] & 0xff); + sum_in_r -= ((stack[stack_i] >>> 16) & 0xff); + sum_in_g -= ((stack[stack_i] >>> 8) & 0xff); + sum_in_b -= (stack[stack_i] & 0xff); + } + } + } + + } + + private static class BlurTask implements Callable { + private final int[] _src; + private final int _w; + private final int _h; + private final int _radius; + private final int _totalCores; + private final int _coreIndex; + private final int _round; + + public BlurTask(int[] src, int w, int h, int radius, int totalCores, int coreIndex, int round) { + _src = src; + _w = w; + _h = h; + _radius = radius; + _totalCores = totalCores; + _coreIndex = coreIndex; + _round = round; + } + + @Override + public Void call() throws Exception { + blurIteration(_src, _w, _h, _radius, _totalCores, _coreIndex, _round); + return null; + } + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.java b/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.java new file mode 100644 index 00000000..896f68ac --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/StopWatch.java @@ -0,0 +1,82 @@ +package code.name.monkey.retromusic.helper; + +/** + * Simple thread safe stop watch. + * + * @author Karim Abou Zeid (kabouzeid) + */ +public class StopWatch { + + /** + * The time the stop watch was last started. + */ + private long startTime; + + /** + * The time elapsed before the current {@link #startTime}. + */ + private long previousElapsedTime; + + /** + * Whether the stop watch is currently running or not. + */ + private boolean isRunning; + + /** + * Starts or continues the stop watch. + * + * @see #pause() + * @see #reset() + */ + public void start() { + synchronized (this) { + startTime = System.currentTimeMillis(); + isRunning = true; + } + } + + /** + * Pauses the stop watch. It can be continued later from {@link #start()}. + * + * @see #start() + * @see #reset() + */ + public void pause() { + synchronized (this) { + previousElapsedTime += System.currentTimeMillis() - startTime; + isRunning = false; + } + } + + /** + * Stops and resets the stop watch to zero milliseconds. + * + * @see #start() + * @see #pause() + */ + public void reset() { + synchronized (this) { + startTime = 0; + previousElapsedTime = 0; + isRunning = false; + } + } + + /** + * @return the total elapsed time in milliseconds + */ + public final long getElapsedTime() { + synchronized (this) { + long currentElapsedTime = 0; + if (isRunning) { + currentElapsedTime = System.currentTimeMillis() - startTime; + } + return previousElapsedTime + currentElapsedTime; + } + } + + @Override + public String toString() { + return String.format("%d millis", getElapsedTime()); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.java new file mode 100644 index 00000000..0c71de2d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/GenreMenuHelper.java @@ -0,0 +1,50 @@ +package code.name.monkey.retromusic.helper.menu; + +import android.app.Activity; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.loaders.GenreLoader; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; + +/** + * @author Hemanth S (h4h13). + */ + +public class GenreMenuHelper { + public static boolean handleMenuClick(@NonNull AppCompatActivity activity, + @NonNull Genre genre, + @NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_play: + MusicPlayerRemote.openQueue(getGenreSongs(activity, genre), 0, true); + return true; + case R.id.action_play_next: + MusicPlayerRemote.playNext(getGenreSongs(activity, genre)); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialog.create(getGenreSongs(activity, genre)) + .show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); + return true; + case R.id.action_add_to_current_playing: + MusicPlayerRemote.enqueue(getGenreSongs(activity, genre)); + return true; + } + return false; + } + + @NonNull + private static ArrayList getGenreSongs(@NonNull Activity activity, + @NonNull Genre genre) { + ArrayList songs; + songs = GenreLoader.getSongs(activity, genre.id).blockingFirst(); + return songs; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.java new file mode 100644 index 00000000..a6944221 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/PlaylistMenuHelper.java @@ -0,0 +1,91 @@ +package code.name.monkey.retromusic.helper.menu; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; +import android.widget.Toast; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog; +import code.name.monkey.retromusic.dialogs.DeletePlaylistDialog; +import code.name.monkey.retromusic.dialogs.RenamePlaylistDialog; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; +import code.name.monkey.retromusic.misc.WeakContextAsyncTask; +import code.name.monkey.retromusic.model.AbsCustomPlaylist; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.PlaylistsUtil; + + +public class PlaylistMenuHelper { + + public static boolean handleMenuClick(@NonNull AppCompatActivity activity, + @NonNull final Playlist playlist, @NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_play: + MusicPlayerRemote.openQueue(getPlaylistSongs(activity, playlist), 9, true); + return true; + case R.id.action_play_next: + MusicPlayerRemote.playNext(getPlaylistSongs(activity, playlist)); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialog.create(getPlaylistSongs(activity, playlist)) + .show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); + return true; + case R.id.action_add_to_current_playing: + MusicPlayerRemote.enqueue(getPlaylistSongs(activity, playlist)); + return true; + case R.id.action_rename_playlist: + RenamePlaylistDialog.create(playlist.id) + .show(activity.getSupportFragmentManager(), "RENAME_PLAYLIST"); + return true; + case R.id.action_delete_playlist: + DeletePlaylistDialog.create(playlist) + .show(activity.getSupportFragmentManager(), "DELETE_PLAYLIST"); + return true; + case R.id.action_save_playlist: + new SavePlaylistAsyncTask(activity).execute(playlist); + return true; + } + return false; + } + + @NonNull + private static ArrayList getPlaylistSongs(@NonNull Activity activity, + @NonNull Playlist playlist) { + ArrayList songs; + if (playlist instanceof AbsCustomPlaylist) { + songs = ((AbsCustomPlaylist) playlist).getSongs(activity).blockingFirst(); + } else { + songs = PlaylistSongsLoader.getPlaylistSongList(activity, playlist).blockingFirst(); + } + return songs; + } + + private static class SavePlaylistAsyncTask extends WeakContextAsyncTask { + SavePlaylistAsyncTask(Context context) { + super(context); + } + + @Override + protected String doInBackground(Playlist... params) { + return String.format(RetroApplication.getInstance().getApplicationContext().getString(R.string + .saved_playlist_to), PlaylistsUtil.savePlaylist(RetroApplication.getInstance().getApplicationContext(), params[0]).blockingFirst()); + } + + @Override + protected void onPostExecute(String string) { + super.onPostExecute(string); + Context context = getContext(); + if (context != null) { + Toast.makeText(context, string, Toast.LENGTH_LONG).show(); + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.java new file mode 100644 index 00000000..5d182886 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongMenuHelper.java @@ -0,0 +1,93 @@ +package code.name.monkey.retromusic.helper.menu; + +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; +import android.view.View; +import android.widget.PopupMenu; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog; +import code.name.monkey.retromusic.dialogs.DeleteSongsDialog; +import code.name.monkey.retromusic.dialogs.SongDetailDialog; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.interfaces.PaletteColorHolder; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.activities.tageditor.AbsTagEditorActivity; +import code.name.monkey.retromusic.ui.activities.tageditor.SongTagEditorActivity; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; + + +public class SongMenuHelper { + public static final int MENU_RES = R.menu.menu_item_song; + + public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull Song song, int menuItemId) { + switch (menuItemId) { + case R.id.action_set_as_ringtone: + MusicUtil.setRingtone(activity, song.id); + return true; + case R.id.action_share: + activity.startActivity(Intent.createChooser(MusicUtil.createShareSongFileIntent(song, activity), null)); + return true; + case R.id.action_delete_from_device: + DeleteSongsDialog.create(song).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialog.create(song).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); + return true; + case R.id.action_play_next: + MusicPlayerRemote.playNext(song); + return true; + case R.id.action_add_to_current_playing: + MusicPlayerRemote.enqueue(song); + return true; + case R.id.action_tag_editor: + Intent tagEditorIntent = new Intent(activity, SongTagEditorActivity.class); + tagEditorIntent.putExtra(AbsTagEditorActivity.EXTRA_ID, song.id); + if (activity instanceof PaletteColorHolder) + tagEditorIntent.putExtra(AbsTagEditorActivity.EXTRA_PALETTE, ((PaletteColorHolder) activity).getPaletteColor()); + activity.startActivity(tagEditorIntent); + return true; + case R.id.action_details: + SongDetailDialog.create(song).show(activity.getSupportFragmentManager(), "SONG_DETAILS"); + return true; + case R.id.action_go_to_album: + NavigationUtil.goToAlbum(activity, song.albumId); + return true; + case R.id.action_go_to_artist: + NavigationUtil.goToArtist(activity, song.artistId); + return true; + } + return false; + } + + public static abstract class OnClickSongMenu implements View.OnClickListener, PopupMenu.OnMenuItemClickListener { + private AppCompatActivity activity; + + protected OnClickSongMenu(@NonNull AppCompatActivity activity) { + this.activity = activity; + } + + public int getMenuRes() { + return MENU_RES; + } + + @Override + public void onClick(View v) { + PopupMenu popupMenu = new PopupMenu(activity, v); + popupMenu.inflate(getMenuRes()); + popupMenu.setOnMenuItemClickListener(this); + popupMenu.show(); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + return handleMenuClick(activity, getSong(), item.getItemId()); + } + + public abstract Song getSong(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.java b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.java new file mode 100644 index 00000000..cb305243 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/helper/menu/SongsMenuHelper.java @@ -0,0 +1,35 @@ +package code.name.monkey.retromusic.helper.menu; + +import android.support.annotation.NonNull; +import android.support.v4.app.FragmentActivity; + +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog; +import code.name.monkey.retromusic.dialogs.DeleteSongsDialog; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; + + + +public class SongsMenuHelper { + public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull ArrayList songs, int menuItemId) { + switch (menuItemId) { + case R.id.action_play_next: + MusicPlayerRemote.playNext(songs); + return true; + case R.id.action_add_to_current_playing: + MusicPlayerRemote.enqueue(songs); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialog.create(songs).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); + return true; + case R.id.action_delete_from_device: + DeleteSongsDialog.create(songs).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); + return true; + } + return false; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/CabHolder.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/CabHolder.java new file mode 100644 index 00000000..76ba6f35 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/CabHolder.java @@ -0,0 +1,12 @@ +package code.name.monkey.retromusic.interfaces; + +import android.support.annotation.NonNull; + +import com.afollestad.materialcab.MaterialCab; + + +public interface CabHolder { + + @NonNull + MaterialCab openCab(final int menuRes, final MaterialCab.Callback callback); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.java new file mode 100644 index 00000000..88143418 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/EqualizerInterface.java @@ -0,0 +1,49 @@ +package code.name.monkey.retromusic.interfaces; + +import android.media.audiofx.BassBoost; +import android.media.audiofx.Equalizer; +import android.media.audiofx.Virtualizer; + +/** + * @author Hemanth S (h4h13). + */ + +public interface EqualizerInterface { + int getBandLevelLow(); + + int getBandLevelHigh(); + + int getNumberOfBands(); + + int getCenterFreq(int band); + + int getBandLevel(int band); + + void setBandLevel(int band, int level); + + boolean isBassBoostEnabled(); + + void setBassBoostEnabled(boolean isEnabled); + + int getBassBoostStrength(); + + void setBassBoostStrength(int strength); + + boolean isVirtualizerEnabled(); + + void setVirtualizerEnabled(boolean isEnabled); + + int getVirtualizerStrength(); + + void setVirtualizerStrength(int strength); + + boolean isRunning(); + + Equalizer getEqualizer(); + + BassBoost getBassBoost(); + + Virtualizer getVirtualizer(); + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.java new file mode 100644 index 00000000..e7792818 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/LoaderIds.java @@ -0,0 +1,6 @@ +package code.name.monkey.retromusic.interfaces; + + +public interface LoaderIds { + int FOLDERS_FRAGMENT = 5; +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/MainActivityFragmentCallbacks.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/MainActivityFragmentCallbacks.java new file mode 100644 index 00000000..fd165510 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/MainActivityFragmentCallbacks.java @@ -0,0 +1,13 @@ +package code.name.monkey.retromusic.interfaces; + +import android.support.v4.app.Fragment; + +/** + * Created by hemanths on 14/08/17. + */ + +public interface MainActivityFragmentCallbacks { + boolean handleBackPress(); + + //void selectedFragment(Fragment fragment); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/MusicServiceEventListener.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/MusicServiceEventListener.java new file mode 100644 index 00000000..ea609019 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/MusicServiceEventListener.java @@ -0,0 +1,20 @@ +package code.name.monkey.retromusic.interfaces; + + +public interface MusicServiceEventListener { + void onServiceConnected(); + + void onServiceDisconnected(); + + void onQueueChanged(); + + void onPlayingMetaChanged(); + + void onPlayStateChanged(); + + void onRepeatModeChanged(); + + void onShuffleModeChanged(); + + void onMediaStoreChanged(); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.java b/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.java new file mode 100644 index 00000000..406ddf60 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/interfaces/PaletteColorHolder.java @@ -0,0 +1,12 @@ +package code.name.monkey.retromusic.interfaces; + +import android.support.annotation.ColorInt; + +/** + * @author Aidan Follestad (afollestad) + */ +public interface PaletteColorHolder { + + @ColorInt + int getPaletteColor(); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/AlbumLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/AlbumLoader.java new file mode 100644 index 00000000..fb12d1dc --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/AlbumLoader.java @@ -0,0 +1,104 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.provider.MediaStore.Audio.AudioColumns; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.Observable; +import java.util.ArrayList; + +/** + * Created by hemanths on 11/08/17. + */ + +public class AlbumLoader { + + public static Observable> getAllAlbums(@NonNull Context context) { + Observable> songs = SongLoader.getSongs(SongLoader.makeSongCursor( + context, + null, + null, + getSongLoaderSortOrder(context)) + ); + + return splitIntoAlbums(songs); + } + + @NonNull + public static Observable> getAlbums(@NonNull final Context context, + String query) { + Observable> songs = SongLoader.getSongs(SongLoader.makeSongCursor( + context, + AudioColumns.ALBUM + " LIKE ?", + new String[]{"%" + query + "%"}, + getSongLoaderSortOrder(context)) + ); + return splitIntoAlbums(songs); + } + + @NonNull + public static Observable getAlbum(@NonNull final Context context, int albumId) { + return Observable.create(e -> { + Observable> songs = SongLoader.getSongs(SongLoader + .makeSongCursor(context, AudioColumns.ALBUM_ID + "=?", + new String[]{String.valueOf(albumId)}, getSongLoaderSortOrder(context))); + songs.subscribe(songs1 -> { + e.onNext(new Album(songs1)); + e.onComplete(); + }); + }); + } + + @NonNull + public static Observable> splitIntoAlbums( + @Nullable final Observable> songs) { + return Observable.create(e -> { + ArrayList albums = new ArrayList<>(); + if (songs != null) { + songs.subscribe(songs1 -> { + for (Song song : songs1) { + getOrCreateAlbum(albums, song.albumId).subscribe(album -> album.songs.add(song)); + } + }); + } + e.onNext(albums); + e.onComplete(); + }); + } + + @NonNull + public static ArrayList splitIntoAlbums(@Nullable final ArrayList songs) { + ArrayList albums = new ArrayList<>(); + if (songs != null) { + for (Song song : songs) { + getOrCreateAlbum(albums, song.albumId).subscribe(album -> album.songs.add(song)); + } + } + return albums; + } + + private static Observable getOrCreateAlbum(ArrayList albums, int albumId) { + return Observable.create(e -> { + for (Album album : albums) { + if (!album.songs.isEmpty() && album.songs.get(0).albumId == albumId) { + e.onNext(album); + e.onComplete(); + return; + } + } + Album album = new Album(); + albums.add(album); + e.onNext(album); + e.onComplete(); + }); + } + + public static String getSongLoaderSortOrder(Context context) { + return PreferenceUtil.getInstance(context).getAlbumSortOrder() + ", " + + //PreferenceUtil.getInstance(context).getAlbumSongSortOrder() + "," + + PreferenceUtil.getInstance(context).getAlbumDetailSongSortOrder(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistLoader.java new file mode 100644 index 00000000..500c98a2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistLoader.java @@ -0,0 +1,119 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.provider.MediaStore.Audio.AudioColumns; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.Observable; + +public class ArtistLoader { + public static String getSongLoaderSortOrder(Context context) { + return PreferenceUtil.getInstance(context).getArtistSortOrder() + ", " + + PreferenceUtil.getInstance(context).getArtistAlbumSortOrder() + ", " + + PreferenceUtil.getInstance(context).getAlbumDetailSongSortOrder() + ", " + + PreferenceUtil.getInstance(context).getArtistDetailSongSortOrder(); + } + + @NonNull + public static Observable getArtist(@NonNull final Context context, int artistId) { + return Observable.create(e -> SongLoader.getSongs(SongLoader.makeSongCursor( + context, + AudioColumns.ARTIST_ID + "=?", + new String[]{String.valueOf(artistId)}, + getSongLoaderSortOrder(context))) + .subscribe(songs -> { + Artist artist = new Artist(AlbumLoader.splitIntoAlbums(songs)); + e.onNext(artist); + e.onComplete(); + })); + } + + @NonNull + public static Observable> getAllArtists(@NonNull final Context context) { + return Observable.create(e -> SongLoader + .getSongs(SongLoader.makeSongCursor( + context, + null, + null, + getSongLoaderSortOrder(context)) + ).subscribe(songs -> { + e.onNext(splitIntoArtists(AlbumLoader.splitIntoAlbums(songs))); + e.onComplete(); + })); + + } + + @NonNull + public static Observable> getArtists(@NonNull final Context context, String query) { + return Observable.create(e -> SongLoader.getSongs(SongLoader.makeSongCursor( + context, + AudioColumns.ARTIST + " LIKE ?", + new String[]{"%" + query + "%"}, + getSongLoaderSortOrder(context)) + ).subscribe(songs -> { + e.onNext(splitIntoArtists(AlbumLoader.splitIntoAlbums(songs))); + e.onComplete(); + })); + } + + @NonNull + public static ArrayList splitIntoArtists(@Nullable final ArrayList albums) { + ArrayList artists = new ArrayList<>(); + if (albums != null) { + for (Album album : albums) { + getOrCreateArtist(artists, album.getArtistId()).albums.add(album); + } + } + return artists; + } + + private static Artist getOrCreateArtist(ArrayList artists, int artistId) { + for (Artist artist : artists) { + if (!artist.albums.isEmpty() && !artist.albums.get(0).songs.isEmpty() && artist.albums.get(0).songs.get(0).artistId == artistId) { + return artist; + } + } + Artist album = new Artist(); + artists.add(album); + return album; + } + + public static Observable> splitIntoArtists(Observable> albums) { + return Observable.create(e -> { + ArrayList artists = new ArrayList<>(); + albums.subscribe(localAlbums -> { + if (localAlbums != null) { + for (Album album : localAlbums) { + getOrCreateArtist(artists, album.getArtistId()).albums.add(album); + } + } + e.onNext(artists); + e.onComplete(); + }); + }); + } + + /* public static Observable> getAllArtists(Context context) { + return getArtistsForCursor(makeArtistCursor(context, null, null)); + } + + public static Observable getArtist(Context context, long id) { + return getArtist(makeArtistCursor(context, "_id=?", new String[]{String.valueOf(id)})); + } + + public static Observable> getArtists(Context context, String paramString) { + return getArtistsForCursor(makeArtistCursor(context, "artist LIKE ?", new String[]{"%" + paramString + "%"})); + } + + private static Cursor makeArtistCursor(Context context, String selection, String[] paramArrayOfString) { + final String artistSortOrder = PreferenceUtil.getInstance(context).getArtistSortOrder(); + Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{"_id", "artist", "number_of_albums", "number_of_tracks"}, selection, paramArrayOfString, artistSortOrder); + return cursor; + }*/ +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistSongLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistSongLoader.java new file mode 100644 index 00000000..e4344ee7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/ArtistSongLoader.java @@ -0,0 +1,37 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.provider.MediaStore; +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.Observable; + + +public class ArtistSongLoader extends SongLoader { + + @NonNull + public static Observable> getArtistSongList(@NonNull final Context context, final int artistId) { + return getSongs(makeArtistSongCursor(context, artistId)); + } + + public static Cursor makeArtistSongCursor(@NonNull final Context context, final int artistId) { + try { + return makeSongCursor( + context, + MediaStore.Audio.AudioColumns.ARTIST_ID + "=?", + new String[]{ + String.valueOf(artistId) + }, + PreferenceUtil.getInstance(context).getArtistSongSortOrder() + ); + } catch (SecurityException e) { + return null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/GenreLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreLoader.java new file mode 100644 index 00000000..5ca28f20 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreLoader.java @@ -0,0 +1,133 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.BaseColumns; +import android.provider.MediaStore.Audio.Genres; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.Observable; + +public class GenreLoader { + + @NonNull + public static Observable> getAllGenres(@NonNull final Context context) { + return getGenresFromCursor(context, makeGenreCursor(context)); + } + + @NonNull + public static Observable> getSongs(@NonNull final Context context, final int genreId) { + // The genres table only stores songs that have a genre specified, + // so we need to get songs without a genre a different way. + if (genreId == -1) { + return getSongsWithNoGenre(context); + } + + return SongLoader.getSongs(makeGenreSongCursor(context, genreId)); + } + + @NonNull + private static Genre getGenreFromCursor(@NonNull Context context, @NonNull final Cursor cursor) { + final int id = cursor.getInt(0); + final String name = cursor.getString(1); + final int songCount = getSongs(context, id).blockingFirst().size(); + return new Genre(id, name, songCount); + + } + + @NonNull + private static Observable> getSongsWithNoGenre(@NonNull final Context context) { + String selection = BaseColumns._ID + " NOT IN " + + "(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"; + return SongLoader.getSongs(SongLoader.makeSongCursor(context, selection, null)); + } + + private static boolean hasSongsWithNoGenre(@NonNull final Context context) { + final Cursor allSongsCursor = SongLoader.makeSongCursor(context, null, null); + final Cursor allSongsWithGenreCursor = makeAllSongsWithGenreCursor(context); + + if (allSongsCursor == null || allSongsWithGenreCursor == null) { + return false; + } + + final boolean hasSongsWithNoGenre = allSongsCursor.getCount() > allSongsWithGenreCursor.getCount(); + allSongsCursor.close(); + allSongsWithGenreCursor.close(); + return hasSongsWithNoGenre; + } + + @Nullable + private static Cursor makeAllSongsWithGenreCursor(@NonNull final Context context) { + try { + return context.getContentResolver().query( + Uri.parse("content://media/external/audio/genres/all/members"), + new String[]{Genres.Members.AUDIO_ID}, null, null, null); + } catch (SecurityException e) { + return null; + } + } + + @Nullable + private static Cursor makeGenreSongCursor(@NonNull final Context context, int genreId) { + try { + return context.getContentResolver().query( + Genres.Members.getContentUri("external", genreId), + SongLoader.BASE_PROJECTION, SongLoader.BASE_SELECTION, null, PreferenceUtil.getInstance(context).getSongSortOrder()); + } catch (SecurityException e) { + return null; + } + } + + @NonNull + private static Observable> getGenresFromCursor(@NonNull final Context context, @Nullable final Cursor cursor) { + return Observable.create(e -> { + final ArrayList genres = new ArrayList<>(); + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + Genre genre = getGenreFromCursor(context, cursor); + if (genre.songCount > 0) { + genres.add(genre); + } else { + // try to remove the empty genre from the media store + try { + context.getContentResolver().delete(Genres.EXTERNAL_CONTENT_URI, Genres._ID + " == " + genre.id, null); + } catch (Exception ex) { + ex.printStackTrace(); + // nothing we can do then + } + } + } while (cursor.moveToNext()); + } + cursor.close(); + } + e.onNext(genres); + e.onComplete(); + }); + } + + + @Nullable + private static Cursor makeGenreCursor(@NonNull final Context context) { + final String[] projection = new String[]{ + Genres._ID, + Genres.NAME + }; + + try { + return context.getContentResolver().query( + Genres.EXTERNAL_CONTENT_URI, + projection, null, null, PreferenceUtil.getInstance(context).getGenreSortOrder()); + } catch (SecurityException e) { + return null; + } + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/GenreSongsLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreSongsLoader.java new file mode 100644 index 00000000..11422e4d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/GenreSongsLoader.java @@ -0,0 +1,78 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.provider.MediaStore; +import android.provider.MediaStore.Audio.AudioColumns; +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Observable; + +/** + * @author Hemanth S (h4h13). + */ + +public class GenreSongsLoader { + + public static Observable> getGenreSongsList(@NonNull Context context, @NonNull int genreId) { + return Observable.create(e -> { + ArrayList list = new ArrayList<>(); + Cursor cursor = makeGenreSongCursor(context, genreId); + if (cursor != null && cursor.moveToFirst()) { + do { + list.add(getGenreSongFromCursorImpl(cursor)); + } while (cursor.moveToNext()); + } + if (cursor != null) { + cursor.close(); + } + e.onNext((ArrayList) (List) list); + e.onComplete(); + }); + } + + @NonNull + private static Song getGenreSongFromCursorImpl(@NonNull Cursor cursor) { + final int id = cursor.getInt(0); + final String title = cursor.getString(1); + final int trackNumber = cursor.getInt(2); + final int year = cursor.getInt(3); + final long duration = cursor.getLong(4); + final String data = cursor.getString(5); + final int dateModified = cursor.getInt(6); + final int albumId = cursor.getInt(7); + final String albumName = cursor.getString(8); + final int artistId = cursor.getInt(9); + final String artistName = cursor.getString(10); + + return new Song(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName); + } + + private static Cursor makeGenreSongCursor(Context context, long genreId) { + try { + return context.getContentResolver().query( + MediaStore.Audio.Genres.Members.getContentUri("external", genreId), + new String[]{ + MediaStore.Audio.Playlists.Members.AUDIO_ID,// 0 + AudioColumns.TITLE,// 1 + AudioColumns.TRACK,// 2 + AudioColumns.YEAR,// 3 + AudioColumns.DURATION,// 4 + AudioColumns.DATA,// 5 + AudioColumns.DATE_MODIFIED,// 6 + AudioColumns.ALBUM_ID,// 7 + AudioColumns.ALBUM,// 8 + AudioColumns.ARTIST_ID,// 9 + AudioColumns.ARTIST,// 10 + }, SongLoader.BASE_SELECTION, null, + MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER); + } catch (SecurityException e) { + return null; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/HomeLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/HomeLoader.java new file mode 100644 index 00000000..783f39ad --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/HomeLoader.java @@ -0,0 +1,60 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.support.annotation.NonNull; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.HistoryPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist; +import io.reactivex.Observable; +import java.util.ArrayList; + +public class HomeLoader { + + public static Observable> getRecentAndTopThings( + @NonNull Context context) { + ArrayList objects = new ArrayList<>(); + return Observable.create(e -> { + + new HistoryPlaylist(context).getSongs(context).subscribe(songs -> { + if (!songs.isEmpty()) { + objects.add(new HistoryPlaylist(context)); + } + }); + new LastAddedPlaylist(context).getSongs(context).subscribe(songs -> { + if (!songs.isEmpty()) { + objects.add(new LastAddedPlaylist(context)); + } + }); + new MyTopTracksPlaylist(context).getSongs(context).subscribe(songs -> { + if (!songs.isEmpty()) { + objects.add(new MyTopTracksPlaylist(context)); + } + }); + + e.onNext(objects); + e.onComplete(); + }); + } + + public static Observable> getHomeLoader(@NonNull Context context) { + ArrayList playlists = new ArrayList<>(); + PlaylistLoader.getAllPlaylists(context) + .subscribe(playlists1 -> { + if (playlists1.size() > 0) { + for (Playlist playlist : playlists1) { + PlaylistSongsLoader.getPlaylistSongList(context, playlist) + .subscribe(songs -> { + if (songs.size() > 0) { + playlists.add(playlist); + } + }); + } + } + }); + return Observable.just(playlists); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/LastAddedSongsLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/LastAddedSongsLoader.java new file mode 100644 index 00000000..0e664c2d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/LastAddedSongsLoader.java @@ -0,0 +1,47 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.provider.MediaStore; + +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.Observable; +import io.reactivex.annotations.NonNull; + +/** + * Created by hemanths on 16/08/17. + */ + +public class LastAddedSongsLoader { + + @NonNull + public static Observable> getLastAddedSongs(@NonNull Context context) { + return SongLoader.getSongs(makeLastAddedCursor(context)); + } + + public static Cursor makeLastAddedCursor(@NonNull final Context context) { + long cutoff = PreferenceUtil.getInstance(context).getLastAddedCutoff(); + + return SongLoader.makeSongCursor( + context, + MediaStore.Audio.Media.DATE_ADDED + ">?", + new String[]{String.valueOf(cutoff)}, + MediaStore.Audio.Media.DATE_ADDED + " DESC"); + } + + @NonNull + public static Observable> getLastAddedAlbums(@NonNull Context context) { + return AlbumLoader.splitIntoAlbums(getLastAddedSongs(context)); + } + + @NonNull + public static Observable> getLastAddedArtists(@NonNull Context context) { + return ArtistLoader.splitIntoArtists(getLastAddedAlbums(context)); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistLoader.java new file mode 100644 index 00000000..70ed160b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistLoader.java @@ -0,0 +1,118 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.BaseColumns; +import android.provider.MediaStore; +import android.provider.MediaStore.Audio.PlaylistsColumns; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import code.name.monkey.retromusic.model.Playlist; + +import java.util.ArrayList; + +import io.reactivex.Observable; + +/** + * Created by hemanths on 16/08/17. + */ + +public class PlaylistLoader { + @Nullable + public static Cursor makePlaylistCursor(@NonNull final Context context, final String selection, final String[] values) { + try { + return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + PlaylistsColumns.NAME + }, selection, values, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER); + } catch (SecurityException e) { + return null; + } + } + + @NonNull + public static Observable getPlaylist(@Nullable final Cursor cursor) { + return Observable.create(e -> { + Playlist playlist = new Playlist(); + + if (cursor != null && cursor.moveToFirst()) { + playlist = getPlaylistFromCursorImpl(cursor); + } + if (cursor != null) + cursor.close(); + + e.onNext(playlist); + e.onComplete(); + }); + + + } + + @NonNull + public static Observable getPlaylist(@NonNull final Context context, final String playlistName) { + return getPlaylist(makePlaylistCursor( + context, + PlaylistsColumns.NAME + "=?", + new String[]{ + playlistName + } + )); + } + + @NonNull + public static Observable getPlaylist(@NonNull final Context context, final int playlistId) { + return getPlaylist(makePlaylistCursor( + context, + BaseColumns._ID + "=?", + new String[]{ + String.valueOf(playlistId) + } + )); + } + + @NonNull + private static Playlist getPlaylistFromCursorImpl(@NonNull final Cursor cursor) { + + final int id = cursor.getInt(0); + final String name = cursor.getString(1); + return new Playlist(id, name); + } + + + @NonNull + public static Observable> getAllPlaylists(@Nullable final Cursor cursor) { + return Observable.create(e -> { + ArrayList playlists = new ArrayList<>(); + + if (cursor != null && cursor.moveToFirst()) { + do { + playlists.add(getPlaylistFromCursorImpl(cursor)); + } while (cursor.moveToNext()); + } + if (cursor != null) + cursor.close(); + + e.onNext(playlists); + e.onComplete(); + }); + } + + @NonNull + public static Observable> getAllPlaylists(@NonNull final Context context) { + return getAllPlaylists(makePlaylistCursor(context, null, null)); + } + + public static void deletePlaylists(Context context, long playlistId) { + Uri localUri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI; + StringBuilder localStringBuilder = new StringBuilder(); + localStringBuilder.append("_id IN ("); + localStringBuilder.append((playlistId)); + localStringBuilder.append(")"); + context.getContentResolver().delete(localUri, localStringBuilder.toString(), null); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistSongsLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistSongsLoader.java new file mode 100644 index 00000000..3c373b60 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/PlaylistSongsLoader.java @@ -0,0 +1,95 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.provider.MediaStore; +import android.provider.MediaStore.Audio.AudioColumns; + +import code.name.monkey.retromusic.model.AbsCustomPlaylist; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.PlaylistSong; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.Observable; +import io.reactivex.annotations.NonNull; + +/** + * Created by hemanths on 16/08/17. + */ + +public class PlaylistSongsLoader { + + @NonNull + public static Observable> getPlaylistSongList(@NonNull Context context, Playlist playlist) { + if (playlist instanceof AbsCustomPlaylist) { + return ((AbsCustomPlaylist) playlist).getSongs(context); + } else { + //noinspection unchecked + return getPlaylistSongList(context, playlist.id); + } + } + + @NonNull + public static Observable> getPlaylistSongList(@NonNull Context context, final int playlistId) { + return Observable.create(e -> { + ArrayList songs = new ArrayList<>(); + Cursor cursor = makePlaylistSongCursor(context, playlistId); + + if (cursor != null && cursor.moveToFirst()) { + do { + songs.add(getPlaylistSongFromCursorImpl(cursor, playlistId)); + } while (cursor.moveToNext()); + } + if (cursor != null) { + cursor.close(); + } + e.onNext((ArrayList) (List) songs); + e.onComplete(); + }); + } + + @NonNull + private static PlaylistSong getPlaylistSongFromCursorImpl(@NonNull Cursor cursor, int playlistId) { + final int id = cursor.getInt(0); + final String title = cursor.getString(1); + final int trackNumber = cursor.getInt(2); + final int year = cursor.getInt(3); + final long duration = cursor.getLong(4); + final String data = cursor.getString(5); + final int dateModified = cursor.getInt(6); + final int albumId = cursor.getInt(7); + final String albumName = cursor.getString(8); + final int artistId = cursor.getInt(9); + final String artistName = cursor.getString(10); + final int idInPlaylist = cursor.getInt(11); + + return new PlaylistSong(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName, playlistId, idInPlaylist); + } + + public static Cursor makePlaylistSongCursor(@NonNull final Context context, final int playlistId) { + try { + return context.getContentResolver().query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[]{ + MediaStore.Audio.Playlists.Members.AUDIO_ID,// 0 + AudioColumns.TITLE,// 1 + AudioColumns.TRACK,// 2 + AudioColumns.YEAR,// 3 + AudioColumns.DURATION,// 4 + AudioColumns.DATA,// 5 + AudioColumns.DATE_MODIFIED,// 6 + AudioColumns.ALBUM_ID,// 7 + AudioColumns.ALBUM,// 8 + AudioColumns.ARTIST_ID,// 9 + AudioColumns.ARTIST,// 10 + MediaStore.Audio.Playlists.Members._ID // 11 + }, SongLoader.BASE_SELECTION, null, + MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); + } catch (SecurityException e) { + return null; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/SearchLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/SearchLoader.java new file mode 100644 index 00000000..f8bbf7a8 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/SearchLoader.java @@ -0,0 +1,44 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import io.reactivex.Observable; + +public class SearchLoader { + + public static Observable> searchAll(@NonNull Context context, @NonNull String query) { + ArrayList results = new ArrayList<>(); + return Observable.create(e -> { + if (!TextUtils.isEmpty(query)) { + SongLoader.getSongs(context, query) + .subscribe(songs -> { + if (!songs.isEmpty()) { + results.add(context.getResources().getString(R.string.songs)); + results.addAll(songs); + } + }); + + ArtistLoader.getArtists(context, query) + .subscribe(artists -> { + if (!artists.isEmpty()) { + results.add(context.getResources().getString(R.string.artists)); + results.addAll(artists); + } + }); + AlbumLoader.getAlbums(context, query) + .subscribe(albums -> { + if (!albums.isEmpty()) { + results.add(context.getResources().getString(R.string.albums)); + results.addAll(albums); + } + }); + } + e.onNext(results); + e.onComplete(); + }); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/SongLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/SongLoader.java new file mode 100644 index 00000000..45e3ed07 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/SongLoader.java @@ -0,0 +1,191 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.provider.MediaStore; +import android.provider.MediaStore.Audio.AudioColumns; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.helper.ShuffleHelper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.providers.BlacklistStore; +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.Observable; + +/** + * Created by hemanths on 10/08/17. + */ + +public class SongLoader { + + protected static final String BASE_SELECTION = + AudioColumns.IS_MUSIC + "=1" + " AND " + AudioColumns.TITLE + " != ''"; + protected static final String[] BASE_PROJECTION = new String[]{ + BaseColumns._ID,// 0 + AudioColumns.TITLE,// 1 + AudioColumns.TRACK,// 2 + AudioColumns.YEAR,// 3 + AudioColumns.DURATION,// 4 + AudioColumns.DATA,// 5 + AudioColumns.DATE_MODIFIED,// 6 + AudioColumns.ALBUM_ID,// 7 + AudioColumns.ALBUM,// 8 + AudioColumns.ARTIST_ID,// 9 + AudioColumns.ARTIST,// 10 + }; + + @NonNull + public static Observable> getAllSongs(@NonNull Context context) { + Cursor cursor = makeSongCursor(context, null, null); + return getSongs(cursor); + } + + @NonNull + public static Observable> getSongs(@NonNull final Context context, + final String query) { + Cursor cursor = makeSongCursor(context, AudioColumns.TITLE + " LIKE ?", + new String[]{"%" + query + "%"}); + return getSongs(cursor); + } + + @NonNull + public static Observable> getSongs(@Nullable final Cursor cursor) { + return Observable.create(e -> { + ArrayList songs = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + songs.add(getSongFromCursorImpl(cursor)); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + e.onNext(songs); + e.onComplete(); + }); + } + + @NonNull + private static Song getSongFromCursorImpl(@NonNull Cursor cursor) { + final int id = cursor.getInt(0); + final String title = cursor.getString(1); + final int trackNumber = cursor.getInt(2); + final int year = cursor.getInt(3); + final long duration = cursor.getLong(4); + final String data = cursor.getString(5); + final long dateModified = cursor.getLong(6); + final int albumId = cursor.getInt(7); + final String albumName = cursor.getString(8); + final int artistId = cursor.getInt(9); + final String artistName = cursor.getString(10); + + return new Song(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, + artistId, artistName); + } + + @Nullable + public static Cursor makeSongCursor(@NonNull final Context context, + @Nullable final String selection, final String[] selectionValues) { + return makeSongCursor(context, selection, selectionValues, + PreferenceUtil.getInstance(context).getSongSortOrder()); + } + + @Nullable + public static Cursor makeSongCursor(@NonNull final Context context, @Nullable String selection, + String[] selectionValues, final String sortOrder) { + if (selection != null && !selection.trim().equals("")) { + selection = BASE_SELECTION + " AND " + selection; + } else { + selection = BASE_SELECTION; + } + + // Blacklist + ArrayList paths = BlacklistStore.getInstance(context).getPaths(); + if (!paths.isEmpty()) { + selection = generateBlacklistSelection(selection, paths.size()); + selectionValues = addBlacklistSelectionValues(selectionValues, paths); + } + + try { + return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + BASE_PROJECTION, selection, selectionValues, sortOrder); + } catch (SecurityException e) { + return null; + } + } + + private static String generateBlacklistSelection(String selection, int pathCount) { + StringBuilder newSelection = new StringBuilder( + selection != null && !selection.trim().equals("") ? selection + " AND " : ""); + newSelection.append(AudioColumns.DATA + " NOT LIKE ?"); + for (int i = 0; i < pathCount - 1; i++) { + newSelection.append(" AND " + AudioColumns.DATA + " NOT LIKE ?"); + } + return newSelection.toString(); + } + + private static String[] addBlacklistSelectionValues(String[] selectionValues, + ArrayList paths) { + if (selectionValues == null) { + selectionValues = new String[0]; + } + String[] newSelectionValues = new String[selectionValues.length + paths.size()]; + System.arraycopy(selectionValues, 0, newSelectionValues, 0, selectionValues.length); + for (int i = selectionValues.length; i < newSelectionValues.length; i++) { + newSelectionValues[i] = paths.get(i - selectionValues.length) + "%"; + } + return newSelectionValues; + } + + @NonNull + public static Observable getSong(@Nullable Cursor cursor) { + return Observable.create(e -> { + Song song; + if (cursor != null && cursor.moveToFirst()) { + song = getSongFromCursorImpl(cursor); + } else { + song = Song.EMPTY_SONG; + } + if (cursor != null) { + cursor.close(); + } + e.onNext(song); + e.onComplete(); + }); + } + + @NonNull + public static Observable getSong(@NonNull final Context context, final int queryId) { + Cursor cursor = makeSongCursor(context, AudioColumns._ID + "=?", + new String[]{String.valueOf(queryId)}); + return getSong(cursor); + } + + public static Observable> suggestSongs(@NonNull Context context) { + return Observable.create(observer -> { + SongLoader.getAllSongs(context) + .subscribe(songs -> { + ArrayList list = new ArrayList<>(); + if (songs.isEmpty()) { + observer.onNext(new ArrayList<>()); + observer.onComplete(); + return; + } + ShuffleHelper.makeShuffleList(songs, -1); + if (songs.size() > 10) { + list.addAll(songs.subList(0, 10)); + } else { + list.addAll(songs); + } + observer.onNext(list); + observer.onComplete(); + }); + }); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/SortedCursor.java b/app/src/main/java/code/name/monkey/retromusic/loaders/SortedCursor.java new file mode 100644 index 00000000..06ab0d27 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/SortedCursor.java @@ -0,0 +1,169 @@ +/* +* Copyright (C) 2014 The CyanogenMod Project +* +* 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 code.name.monkey.retromusic.loaders; + +import android.database.AbstractCursor; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +/** + * This cursor basically wraps a song cursor and is given a list of the order of the ids of the + * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted + * by moving the point to the appropriate spot + */ +public class SortedCursor extends AbstractCursor { + // cursor to wrap + private final Cursor mCursor; + // the map of external indices to internal indices + private ArrayList mOrderedPositions; + // this contains the ids that weren't found in the underlying cursor + private ArrayList mMissingValues; + // this contains the mapped cursor positions and afterwards the extra ids that weren't found + private HashMap mMapCursorPositions; + + /** + * @param cursor to wrap + * @param order the list of unique ids in sorted order to display + * @param columnName the column name of the id to look up in the internal cursor + */ + public SortedCursor(@NonNull final Cursor cursor, @Nullable final String[] order, final String columnName) { + mCursor = cursor; + mMissingValues = buildCursorPositionMapping(order, columnName); + } + + /** + * This function populates mOrderedPositions with the cursor positions in the order based + * on the order passed in + * + * @param order the target order of the internal cursor + * @return returns the ids that aren't found in the underlying cursor + */ + @NonNull + private ArrayList buildCursorPositionMapping(@Nullable final String[] order, final String columnName) { + ArrayList missingValues = new ArrayList<>(); + + mOrderedPositions = new ArrayList<>(mCursor.getCount()); + + mMapCursorPositions = new HashMap<>(mCursor.getCount()); + final int valueColumnIndex = mCursor.getColumnIndex(columnName); + + if (mCursor.moveToFirst()) { + // first figure out where each of the ids are in the cursor + do { + mMapCursorPositions.put(mCursor.getString(valueColumnIndex), mCursor.getPosition()); + } while (mCursor.moveToNext()); + + if (order != null) { + // now create the ordered positions to map to the internal cursor given the + // external sort order + for (final String value : order) { + if (mMapCursorPositions.containsKey(value)) { + mOrderedPositions.add(mMapCursorPositions.get(value)); + mMapCursorPositions.remove(value); + } else { + missingValues.add(value); + } + } + } + + mCursor.moveToFirst(); + } + + return missingValues; + } + + /** + * @return the list of ids that weren't found in the underlying cursor + */ + public ArrayList getMissingValues() { + return mMissingValues; + } + + /** + * @return the list of ids that were in the underlying cursor but not part of the ordered list + */ + @NonNull + public Collection getExtraValues() { + return mMapCursorPositions.keySet(); + } + + @Override + public void close() { + mCursor.close(); + + super.close(); + } + + @Override + public int getCount() { + return mOrderedPositions.size(); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public String getString(int column) { + return mCursor.getString(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + if (newPosition >= 0 && newPosition < getCount()) { + mCursor.moveToPosition(mOrderedPositions.get(newPosition)); + return true; + } + + return false; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/SortedLongCursor.java b/app/src/main/java/code/name/monkey/retromusic/loaders/SortedLongCursor.java new file mode 100644 index 00000000..aea82900 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/SortedLongCursor.java @@ -0,0 +1,169 @@ +/* +* Copyright (C) 2014 The CyanogenMod Project +* +* 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 code.name.monkey.retromusic.loaders; + +import android.database.AbstractCursor; +import android.database.Cursor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +/** + * This cursor basically wraps a song cursor and is given a list of the order of the ids of the + * contents of the cursor. It wraps the Cursor and simulates the internal cursor being sorted + * by moving the point to the appropriate spot + */ +public class SortedLongCursor extends AbstractCursor { + // cursor to wrap + private final Cursor mCursor; + // the map of external indices to internal indices + private ArrayList mOrderedPositions; + // this contains the ids that weren't found in the underlying cursor + private ArrayList mMissingIds; + // this contains the mapped cursor positions and afterwards the extra ids that weren't found + private HashMap mMapCursorPositions; + + /** + * @param cursor to wrap + * @param order the list of unique ids in sorted order to display + * @param columnName the column name of the id to look up in the internal cursor + */ + public SortedLongCursor(final Cursor cursor, final long[] order, final String columnName) { + + mCursor = cursor; + mMissingIds = buildCursorPositionMapping(order, columnName); + } + + /** + * This function populates mOrderedPositions with the cursor positions in the order based + * on the order passed in + * + * @param order the target order of the internal cursor + * @return returns the ids that aren't found in the underlying cursor + */ + @NonNull + private ArrayList buildCursorPositionMapping(@Nullable final long[] order, final String columnName) { + ArrayList missingIds = new ArrayList<>(); + + mOrderedPositions = new ArrayList<>(mCursor.getCount()); + + mMapCursorPositions = new HashMap<>(mCursor.getCount()); + final int idPosition = mCursor.getColumnIndex(columnName); + + if (mCursor.moveToFirst()) { + // first figure out where each of the ids are in the cursor + do { + mMapCursorPositions.put(mCursor.getLong(idPosition), mCursor.getPosition()); + } while (mCursor.moveToNext()); + + // now create the ordered positions to map to the internal cursor given the + // external sort order + for (int i = 0; order != null && i < order.length; i++) { + final long id = order[i]; + if (mMapCursorPositions.containsKey(id)) { + mOrderedPositions.add(mMapCursorPositions.get(id)); + mMapCursorPositions.remove(id); + } else { + missingIds.add(id); + } + } + + mCursor.moveToFirst(); + } + + return missingIds; + } + + /** + * @return the list of ids that weren't found in the underlying cursor + */ + public ArrayList getMissingIds() { + return mMissingIds; + } + + /** + * @return the list of ids that were in the underlying cursor but not part of the ordered list + */ + @NonNull + public Collection getExtraIds() { + return mMapCursorPositions.keySet(); + } + + @Override + public void close() { + mCursor.close(); + + super.close(); + } + + @Override + public int getCount() { + return mOrderedPositions.size(); + } + + @Override + public String[] getColumnNames() { + return mCursor.getColumnNames(); + } + + @Override + public String getString(int column) { + return mCursor.getString(column); + } + + @Override + public short getShort(int column) { + return mCursor.getShort(column); + } + + @Override + public int getInt(int column) { + return mCursor.getInt(column); + } + + @Override + public long getLong(int column) { + return mCursor.getLong(column); + } + + @Override + public float getFloat(int column) { + return mCursor.getFloat(column); + } + + @Override + public double getDouble(int column) { + return mCursor.getDouble(column); + } + + @Override + public boolean isNull(int column) { + return mCursor.isNull(column); + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + if (newPosition >= 0 && newPosition < getCount()) { + mCursor.moveToPosition(mOrderedPositions.get(newPosition)); + return true; + } + + return false; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/loaders/TopAndRecentlyPlayedTracksLoader.java b/app/src/main/java/code/name/monkey/retromusic/loaders/TopAndRecentlyPlayedTracksLoader.java new file mode 100644 index 00000000..1bf59552 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/loaders/TopAndRecentlyPlayedTracksLoader.java @@ -0,0 +1,158 @@ +package code.name.monkey.retromusic.loaders; + +import android.content.Context; +import android.database.Cursor; +import android.provider.BaseColumns; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.providers.HistoryStore; +import code.name.monkey.retromusic.providers.SongPlayCountStore; +import io.reactivex.Observable; +import java.util.ArrayList; + +/** + * Created by hemanths on 16/08/17. + */ + +public class TopAndRecentlyPlayedTracksLoader { + + private static final int NUMBER_OF_TOP_TRACKS = 99; + + @NonNull + public static Observable> getRecentlyPlayedTracks(@NonNull Context context) { + return SongLoader.getSongs(makeRecentTracksCursorAndClearUpDatabase(context)); + } + + @NonNull + public static Observable> getTopTracks(@NonNull Context context) { + return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context)); + } + + @Nullable + private static Cursor makeRecentTracksCursorAndClearUpDatabase(@NonNull final Context context) { + SortedLongCursor retCursor = makeRecentTracksCursorImpl(context); + + // clean up the databases with any ids not found + if (retCursor != null) { + ArrayList missingIds = retCursor.getMissingIds(); + if (missingIds != null && missingIds.size() > 0) { + for (long id : missingIds) { + HistoryStore.getInstance(context).removeSongId(id); + } + } + } + return retCursor; + } + + @Nullable + private static Cursor makeTopTracksCursorAndClearUpDatabase(@NonNull final Context context) { + SortedLongCursor retCursor = makeTopTracksCursorImpl(context); + + // clean up the databases with any ids not found + if (retCursor != null) { + ArrayList missingIds = retCursor.getMissingIds(); + if (missingIds != null && missingIds.size() > 0) { + for (long id : missingIds) { + SongPlayCountStore.getInstance(context).removeItem(id); + } + } + } + return retCursor; + } + + @Nullable + private static SortedLongCursor makeRecentTracksCursorImpl(@NonNull final Context context) { + // first get the top results ids from the internal database + Cursor songs = HistoryStore.getInstance(context).queryRecentIds(); + + try { + return makeSortedCursor(context, songs, + songs.getColumnIndex(HistoryStore.RecentStoreColumns.ID)); + } finally { + if (songs != null) { + songs.close(); + } + } + } + + @Nullable + private static SortedLongCursor makeTopTracksCursorImpl(@NonNull final Context context) { + // first get the top results ids from the internal database + Cursor songs = SongPlayCountStore.getInstance(context) + .getTopPlayedResults(NUMBER_OF_TOP_TRACKS); + + try { + return makeSortedCursor(context, songs, + songs.getColumnIndex(SongPlayCountStore.SongPlayCountColumns.ID)); + } finally { + if (songs != null) { + songs.close(); + } + } + } + + @Nullable + private static SortedLongCursor makeSortedCursor(@NonNull final Context context, + @Nullable final Cursor cursor, final int idColumn) { + + if (cursor != null && cursor.moveToFirst()) { + // create the list of ids to select against + StringBuilder selection = new StringBuilder(); + selection.append(BaseColumns._ID); + selection.append(" IN ("); + + // this tracks the order of the ids + long[] order = new long[cursor.getCount()]; + + long id = cursor.getLong(idColumn); + selection.append(id); + order[cursor.getPosition()] = id; + + while (cursor.moveToNext()) { + selection.append(","); + + id = cursor.getLong(idColumn); + order[cursor.getPosition()] = id; + selection.append(String.valueOf(id)); + } + + selection.append(")"); + + // get a list of songs with the data given the selection statement + Cursor songCursor = SongLoader.makeSongCursor(context, selection.toString(), null); + if (songCursor != null) { + // now return the wrapped TopTracksCursor to handle sorting given order + return new SortedLongCursor(songCursor, order, BaseColumns._ID); + } + } + + return null; + } + + @NonNull + public static Observable> getTopAlbums(@NonNull Context context) { + return Observable.create(e -> { + getTopTracks(context).subscribe(songs -> { + if (songs.size() > 0) { + e.onNext(AlbumLoader.splitIntoAlbums(songs)); + } + e.onComplete(); + }); + }); + } + + @NonNull + public static Observable> getTopArtists(@NonNull Context context) { + return Observable.create(e -> { + getTopAlbums(context).subscribe(albums -> { + if (albums.size() > 0) { + e.onNext(ArtistLoader.splitIntoArtists(albums)); + } + e.onComplete(); + }); + }); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/KogouLyricsFetcher.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/KogouLyricsFetcher.java new file mode 100644 index 00000000..02ded432 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/KogouLyricsFetcher.java @@ -0,0 +1,69 @@ +package code.name.monkey.retromusic.lyrics; + +import android.os.Handler; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.rest.KogouClient; +import code.name.monkey.retromusic.rest.model.KuGouSearchLyricResult; +import code.name.monkey.retromusic.util.LyricUtil; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import java.io.File; + +/** + * @author Hemanth S (h4h13). + */ + +public class KogouLyricsFetcher { + + private KogouClient mKogouClient; + private Song mSong; + private KogouLyricsCallback mCallback; + + public KogouLyricsFetcher(KogouLyricsCallback callback) { + mCallback = callback; + mKogouClient = new KogouClient(); + } + + public void loadLyrics(Song song, String duration) { + mSong = song; + mKogouClient.getApiService() + .searchLyric(mSong.title, duration) + .subscribe(this::parseKugouResult, + throwable -> mCallback.onNoLyrics()); + } + + private void parseKugouResult(KuGouSearchLyricResult kuGouSearchLyricResult) { + if (kuGouSearchLyricResult != null && kuGouSearchLyricResult.status == 200 & + kuGouSearchLyricResult.candidates != null && + kuGouSearchLyricResult.candidates.size() != 0) { + KuGouSearchLyricResult.Candidates candidates = kuGouSearchLyricResult.candidates.get(0); + loadLyricsFile(candidates); + } else { + mCallback.onNoLyrics(); + } + } + + private void loadLyricsFile(KuGouSearchLyricResult.Candidates candidates) { + mKogouClient.getApiService().getRawLyric(candidates.id, candidates.accesskey) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribe(kuGouRawLyric -> { + if (kuGouRawLyric == null) { + mCallback.onNoLyrics(); + return; + } + String rawLyric = LyricUtil.decryptBASE64(kuGouRawLyric.content); + LyricUtil.writeLrcToLoc(mSong.title, mSong.artistName, rawLyric); + new Handler().postDelayed( + () -> mCallback.onLyrics(LyricUtil.getLocalLyricFile(mSong.title, mSong.artistName)), + 1); + }); + } + + public interface KogouLyricsCallback { + + void onNoLyrics(); + + void onLyrics(File file); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LyricsEngine.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LyricsEngine.java new file mode 100644 index 00000000..52884ce8 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LyricsEngine.java @@ -0,0 +1,6 @@ +package code.name.monkey.retromusic.lyrics; + +public interface LyricsEngine { + String getLyrics(String artistName, String songTitle); + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/LyricsWikiEngine.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/LyricsWikiEngine.java new file mode 100644 index 00000000..fde1fa5b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/LyricsWikiEngine.java @@ -0,0 +1,143 @@ +package code.name.monkey.retromusic.lyrics; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.net.URL; + +import javax.net.ssl.HttpsURLConnection; + +public class LyricsWikiEngine implements LyricsEngine { + + private static final String TAG = LyricsWikiEngine.class.getSimpleName(); + + // Reads an InputStream and converts it to a String. + private static String readIt(InputStream stream) throws IOException { + Reader reader = new InputStreamReader(stream, "UTF-8"); + StringWriter sw = new StringWriter(); + char[] buffer = new char[4096]; + int count; + while ((count = reader.read(buffer)) != -1) { + sw.write(buffer, 0, count); + } + + return sw.toString(); + } + + @Override + public String getLyrics(String artistName, String songTitle) { + try { + + String lyricsUrl = makeApiCall(artistName, songTitle); + if (lyricsUrl == null) { // no URL in API answer or no correct answer at all + return null; + } + + return parseFullLyricsPage(lyricsUrl); + + } catch (IOException e) { + Log.w(TAG, "Couldn't connect to lyrics wiki REST endpoints", e); + return null; + } catch (JSONException e) { + Log.w(TAG, "Couldn't transform API answer to JSON entity", e); + return null; + } + } + + /** + * First call + */ + private String makeApiCall(String artistName, String songTitle) throws IOException, JSONException { + HttpsURLConnection apiCall = null; + try { + // build query + // e.g. https://lyrics.wikia.com/api.php?func=getSong&artist=The%20Beatle&song=Girl&fmt=realjson + Uri link = new Uri.Builder() + .scheme("https") + .authority("lyrics.wikia.com") + .path("api.php") + .appendQueryParameter("func", "getSong") + .appendQueryParameter("fmt", "realjson") + .appendQueryParameter("artist", artistName) + .appendQueryParameter("song", songTitle) + .build(); + + // construct an http request + apiCall = (HttpsURLConnection) new URL(link.toString()).openConnection(); + apiCall.setReadTimeout(10_000); + apiCall.setConnectTimeout(15_000); + + // execute + apiCall.connect(); + int response = apiCall.getResponseCode(); + if (response != HttpsURLConnection.HTTP_OK) { + // redirects are handled internally, this is clearly an error + return null; + } + + InputStream is = apiCall.getInputStream(); + String reply = readIt(is); + JSONObject getSongAnswer = new JSONObject(reply); + + return getLyricsUrl(getSongAnswer); + } finally { + if (apiCall != null) { + apiCall.disconnect(); + } + } + } + + /** + * Second call + */ + private String parseFullLyricsPage(String lyricsUrl) throws IOException { + Document page = Jsoup.parse(new URL(lyricsUrl), 10_000); + Element lyricsBox = page.select("div.lyricbox").first(); + if (lyricsBox == null) { // no lyrics frame on page + return null; + } + + // remove unneeded elements + lyricsBox.select("div.rtMatcher").remove(); + lyricsBox.select("div.lyricsbreak").remove(); + lyricsBox.select("script").remove(); + + StringBuilder builder = new StringBuilder(); + for (Node curr : lyricsBox.childNodes()) { + if (curr instanceof TextNode) { + builder.append(((TextNode) curr).text()); + } else { + builder.append("\n"); + } + } + return builder.toString(); + } + + private String getLyricsUrl(JSONObject getSongAnswer) { + try { + String pageId = getSongAnswer.getString("page_id"); + if (TextUtils.isEmpty(pageId)) { + return null; // empty page_id means page wasn't created + } + return getSongAnswer.getString("url"); + } catch (JSONException e) { + Log.w(TAG, "Unknown format of getSong API call answer", e); + return null; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/ParseLyrics.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/ParseLyrics.java new file mode 100644 index 00000000..17f88814 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/ParseLyrics.java @@ -0,0 +1,38 @@ +package code.name.monkey.retromusic.lyrics; + +import android.os.AsyncTask; +import android.text.TextUtils; + +/** + * @author Hemanth S (h4h13). + */ + +public class ParseLyrics extends AsyncTask { + private LyricsCallback mLyricsCallback; + + public ParseLyrics(LyricsCallback lyricsCallback) { + mLyricsCallback = lyricsCallback; + } + + @Override + protected String doInBackground(String... strings) { + LyricsEngine lyricsEngine = new LyricsWikiEngine(); + return lyricsEngine.getLyrics(strings[1], strings[0]); + } + + @Override + protected void onPostExecute(String s) { + super.onPostExecute(s); + if (TextUtils.isEmpty(s)) { + mLyricsCallback.onError(); + return; + } + mLyricsCallback.onShowLyrics(s); + } + + public interface LyricsCallback { + void onShowLyrics(String lyrics); + + void onError(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/lyrics/QueryResult.java b/app/src/main/java/code/name/monkey/retromusic/lyrics/QueryResult.java new file mode 100644 index 00000000..aa70f50c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/lyrics/QueryResult.java @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic.lyrics; + +public class QueryResult { + public static final String ITEM_LRC = "lrc"; + public static final String ATTRIBUTE_ID = "id"; + public static final String ATTRIBUTE_ARTIST = "artist"; + public static final String ATTRIBUTE_TITLE = "title"; + + public final int mId; + public final String mArtist; + public final String mTitle; + + QueryResult(int id, String artist, String title) { + mId = id; + mArtist = artist; + mTitle = title; + } + + @Override + public String toString() { + return mTitle + " - " + mArtist; + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/AppBarStateChangeListener.java b/app/src/main/java/code/name/monkey/retromusic/misc/AppBarStateChangeListener.java new file mode 100644 index 00000000..0f1f8a92 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/AppBarStateChangeListener.java @@ -0,0 +1,41 @@ +package code.name.monkey.retromusic.misc; + +import android.support.design.widget.AppBarLayout; + +/** + * @author Hemanth S (h4h13). + * https://stackoverflow.com/a/33891727 + */ + +public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener { + + private State mCurrentState = State.IDLE; + + @Override + public final void onOffsetChanged(AppBarLayout appBarLayout, int i) { + if (i == 0) { + if (mCurrentState != State.EXPANDED) { + onStateChanged(appBarLayout, State.EXPANDED); + } + mCurrentState = State.EXPANDED; + } else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) { + if (mCurrentState != State.COLLAPSED) { + onStateChanged(appBarLayout, State.COLLAPSED); + } + mCurrentState = State.COLLAPSED; + } else { + if (mCurrentState != State.IDLE) { + onStateChanged(appBarLayout, State.IDLE); + } + mCurrentState = State.IDLE; + } + } + + public abstract void onStateChanged(AppBarLayout appBarLayout, State state); + + public enum State { + EXPANDED, + COLLAPSED, + IDLE + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java b/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java new file mode 100644 index 00000000..ee733845 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/CustomFragmentStatePagerAdapter.java @@ -0,0 +1,237 @@ +package code.name.monkey.retromusic.misc; + +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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. + */ + +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +/** + * Implementation of {@link android.support.v4.view.PagerAdapter} that + * uses a {@link Fragment} to manage each page. This class also handles + * saving and restoring of fragment's state. + *

+ *

This version of the pager is more useful when there are a large number + * of pages, working more like a list view. When pages are not visible to + * the user, their entire fragment may be destroyed, only keeping the saved + * state of that fragment. This allows the pager to hold on to much less + * memory associated with each visited page as compared to + * {@link FragmentPagerAdapter} at the cost of potentially more overhead when + * switching between pages. + *

+ *

When using FragmentPagerAdapter the host ViewPager must have a + * valid ID set.

+ *

+ *

Subclasses only need to implement {@link #getItem(int)} + * and {@link #getCount()} to have a working adapter. + *

+ *

Here is an example implementation of a pager containing fragments of + * lists: + *

+ * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java + * complete} + *

+ *

The R.layout.fragment_pager resource of the top-level fragment is: + *

+ * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml + * complete} + *

+ *

The R.layout.fragment_pager_list resource containing each + * individual fragment's layout is: + *

+ * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml + * complete} + */ +public abstract class CustomFragmentStatePagerAdapter extends android.support.v4.view.PagerAdapter { + public static final String TAG = CustomFragmentStatePagerAdapter.class.getSimpleName(); + private static final boolean DEBUG = false; + + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; + + private ArrayList mSavedState = new ArrayList(); + private ArrayList mFragments = new ArrayList(); + private Fragment mCurrentPrimaryItem = null; + + public CustomFragmentStatePagerAdapter(FragmentManager fm) { + mFragmentManager = fm; + } + + /** + * Return the Fragment associated with a specified position. + */ + public abstract Fragment getItem(int position); + + @Override + public void startUpdate(ViewGroup container) { + } + + @NonNull + @Override + public Object instantiateItem(ViewGroup container, int position) { + // If we already have this item instantiated, there is nothing + // to do. This can happen when we are restoring the entire pager + // from its saved state, where the fragment manager has already + // taken care of restoring the fragments we previously had instantiated. + if (mFragments.size() > position) { + Fragment f = mFragments.get(position); + if (f != null) { + return f; + } + } + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + Fragment fragment = getItem(position); + if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); + if (mSavedState.size() > position) { + Fragment.SavedState fss = mSavedState.get(position); + if (fss != null) { + fragment.setInitialSavedState(fss); + } + } + while (mFragments.size() <= position) { + mFragments.add(null); + } + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + mFragments.set(position, fragment); + mCurTransaction.add(container.getId(), fragment); + + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + + " v=" + ((Fragment) object).getView()); + while (mSavedState.size() <= position) { + mSavedState.add(null); + } + mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); + mFragments.set(position, null); + + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public void finishUpdate(ViewGroup container) { + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment) object).getView() == view; + } + + @Override + public Parcelable saveState() { + Bundle state = null; + if (mSavedState.size() > 0) { + state = new Bundle(); + Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; + mSavedState.toArray(fss); + state.putParcelableArray("states", fss); + } + for (int i = 0; i < mFragments.size(); i++) { + Fragment f = mFragments.get(i); + if (f != null && f.isAdded()) { + if (state == null) { + state = new Bundle(); + } + String key = "f" + i; + mFragmentManager.putFragment(state, key, f); + } + } + return state; + } + + @Override + public void restoreState(Parcelable state, ClassLoader loader) { + if (state != null) { + Bundle bundle = (Bundle) state; + bundle.setClassLoader(loader); + Parcelable[] fss = bundle.getParcelableArray("states"); + mSavedState.clear(); + mFragments.clear(); + if (fss != null) { + for (int i = 0; i < fss.length; i++) { + mSavedState.add((Fragment.SavedState) fss[i]); + } + } + Iterable keys = bundle.keySet(); + for (String key : keys) { + if (key.startsWith("f")) { + int index = Integer.parseInt(key.substring(1)); + Fragment f = mFragmentManager.getFragment(bundle, key); + if (f != null) { + while (mFragments.size() <= index) { + mFragments.add(null); + } + f.setMenuVisibility(false); + mFragments.set(index, f); + } else { + Log.w(TAG, "Bad fragment at key " + key); + } + } + } + } + } + + public Fragment getFragment(int position) { + if (position < mFragments.size() && position >= 0) { + return mFragments.get(position); + } + return null; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java new file mode 100644 index 00000000..8dd39943 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/DialogAsyncTask.java @@ -0,0 +1,90 @@ +package code.name.monkey.retromusic.misc; + +import android.app.Dialog; +import android.content.Context; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.ref.WeakReference; + + +public abstract class DialogAsyncTask extends WeakContextAsyncTask { + private final int delay; + private WeakReference

dialogWeakReference; + + private boolean supposedToBeDismissed; + + public DialogAsyncTask(Context context) { + this(context, 0); + } + + public DialogAsyncTask(Context context, int showDelay) { + super(context); + this.delay = showDelay; + dialogWeakReference = new WeakReference<>(null); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (delay > 0) { + new Handler().postDelayed(this::initAndShowDialog, delay); + } else { + initAndShowDialog(); + } + } + + private void initAndShowDialog() { + Context context = getContext(); + if (!supposedToBeDismissed && context != null) { + Dialog dialog = createDialog(context); + dialogWeakReference = new WeakReference<>(dialog); + dialog.show(); + } + } + + @SuppressWarnings("unchecked") + @Override + protected void onProgressUpdate(Progress... values) { + super.onProgressUpdate(values); + Dialog dialog = getDialog(); + if (dialog != null) { + onProgressUpdate(dialog, values); + } + } + + @SuppressWarnings("unchecked") + protected void onProgressUpdate(@NonNull Dialog dialog, Progress... values) { + } + + @Nullable + protected Dialog getDialog() { + return dialogWeakReference.get(); + } + + @Override + protected void onCancelled(Result result) { + super.onCancelled(result); + tryToDismiss(); + } + + @Override + protected void onPostExecute(Result result) { + super.onPostExecute(result); + tryToDismiss(); + } + + private void tryToDismiss() { + supposedToBeDismissed = true; + try { + Dialog dialog = getDialog(); + if (dialog != null) + dialog.dismiss(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + protected abstract Dialog createDialog(@NonNull Context context); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java b/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java new file mode 100755 index 00000000..da7e7f64 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/LagTracker.java @@ -0,0 +1,62 @@ +package code.name.monkey.retromusic.misc; + +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class LagTracker { + private static Map mMap; + private static LagTracker mSingleton; + private boolean mEnabled = true; + + private LagTracker() { + mMap = new HashMap(); + } + + public static LagTracker get() { + if (mSingleton == null) { + mSingleton = new LagTracker(); + } + return mSingleton; + } + + private void print(String str, long j) { + long toMillis = TimeUnit.NANOSECONDS.toMillis(j); + Log.d("LagTracker", "[" + str + " completed in]: " + j + " ns (" + toMillis + "ms, " + TimeUnit.NANOSECONDS.toSeconds(j) + "s)"); + } + + public LagTracker disable() { + this.mEnabled = false; + return this; + } + + public LagTracker enable() { + this.mEnabled = true; + return this; + } + + public void end(String str) { + long nanoTime = System.nanoTime(); + if (this.mEnabled) { + if (mMap.containsKey(str)) { + print(str, nanoTime - mMap.get(str).longValue()); + mMap.remove(str); + return; + } + throw new IllegalStateException("No start time found for " + str); + } else if (!mMap.isEmpty()) { + mMap.clear(); + } + } + + public void start(String str) { + long nanoTime = System.nanoTime(); + if (this.mEnabled) { + mMap.put(str, Long.valueOf(nanoTime)); + } else if (!mMap.isEmpty()) { + mMap.clear(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/ScrollAwareFABBehavior.java b/app/src/main/java/code/name/monkey/retromusic/misc/ScrollAwareFABBehavior.java new file mode 100644 index 00000000..a189d20c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/ScrollAwareFABBehavior.java @@ -0,0 +1,110 @@ +package code.name.monkey.retromusic.misc; + +import android.content.Context; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.animation.LinearInterpolator; + +/*Don't delete even if its not showing not using*/ +public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior { + private static final String TAG = "ScrollingFABBehavior"; + Handler mHandler; + + public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { + super(); + } + + @Override + public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, + @NonNull FloatingActionButton child, + @NonNull View target) { + super.onStopNestedScroll(coordinatorLayout, child, target); + + if (mHandler == null) + mHandler = new Handler(); + + + mHandler.postDelayed(() -> { + child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start(); + Log.d("FabAnim", "startHandler()"); + }, 1000); + + } + + @Override + public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, + @NonNull FloatingActionButton child, + @NonNull View target, + int dxConsumed, + int dyConsumed, + int dxUnconsumed, + int dyUnconsumed) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); + + //child -> Floating Action Button + if (dyConsumed > 0) { + Log.d("Scrolling", "Up"); + CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); + int fab_bottomMargin = layoutParams.bottomMargin; + child.animate().translationY(child.getHeight() + fab_bottomMargin).setInterpolator(new LinearInterpolator()).start(); + } else if (dyConsumed < 0) { + Log.d("Scrolling", "down"); + child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start(); + } + } + + @Override + public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, + @NonNull FloatingActionButton child, + @NonNull View directTargetChild, + @NonNull View target, + int nestedScrollAxes) { + if (mHandler != null) { + mHandler.removeMessages(0); + Log.d("Scrolling", "stopHandler()"); + } + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; + } +} +/*extends FloatingActionButton.Behavior { + + public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { + super(); + } + + + @Override + public boolean onStartNestedScroll(@NonNull final CoordinatorLayout coordinatorLayout, + @NonNull final FloatingActionButton child, + @NonNull final View directTargetChild, + @NonNull final View target, + final int nestedScrollAxes) { + // Ensure we react to vertical scrolling + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL + || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); + } + + @Override + public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, + @NonNull FloatingActionButton child, + @NonNull View target, + int dxConsumed, + int dyConsumed, + int dxUnconsumed, + int dyUnconsumed) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, + dyUnconsumed); + + if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { + child.hide(); + } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { + child.show(); + } + } +}*/ \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/SimpleAnimatorListener.java b/app/src/main/java/code/name/monkey/retromusic/misc/SimpleAnimatorListener.java new file mode 100644 index 00000000..2f405577 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/SimpleAnimatorListener.java @@ -0,0 +1,26 @@ +package code.name.monkey.retromusic.misc; + +import android.animation.Animator; + + +public abstract class SimpleAnimatorListener implements Animator.AnimatorListener { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/SimpleOnSeekbarChangeListener.java b/app/src/main/java/code/name/monkey/retromusic/misc/SimpleOnSeekbarChangeListener.java new file mode 100644 index 00000000..44a39f6e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/SimpleOnSeekbarChangeListener.java @@ -0,0 +1,21 @@ +package code.name.monkey.retromusic.misc; + +import android.widget.SeekBar; + + +public abstract class SimpleOnSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/UpdateToastMediaScannerCompletionListener.java b/app/src/main/java/code/name/monkey/retromusic/misc/UpdateToastMediaScannerCompletionListener.java new file mode 100644 index 00000000..d9e3496a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/UpdateToastMediaScannerCompletionListener.java @@ -0,0 +1,50 @@ +package code.name.monkey.retromusic.misc; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.widget.Toast; + +import java.lang.ref.WeakReference; + +import code.name.monkey.retromusic.R; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public class UpdateToastMediaScannerCompletionListener implements MediaScannerConnection.OnScanCompletedListener { + private final String[] toBeScanned; + private final String scannedFiles; + private final String couldNotScanFiles; + private final WeakReference activityWeakReference; + private int scanned = 0; + private int failed = 0; + private Toast toast; + + @SuppressLint("ShowToast") + public UpdateToastMediaScannerCompletionListener(Activity activity, String[] toBeScanned) { + this.toBeScanned = toBeScanned; + scannedFiles = activity.getString(R.string.scanned_files); + couldNotScanFiles = activity.getString(R.string.could_not_scan_files); + toast = Toast.makeText(activity.getApplicationContext(), "", Toast.LENGTH_SHORT); + activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void onScanCompleted(final String path, final Uri uri) { + Activity activity = activityWeakReference.get(); + if (activity != null) { + activity.runOnUiThread(() -> { + if (uri == null) { + failed++; + } else { + scanned++; + } + String text = " " + String.format(scannedFiles, scanned, toBeScanned.length) + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : ""); + toast.setText(text); + toast.show(); + }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/WeakContextAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/misc/WeakContextAsyncTask.java new file mode 100644 index 00000000..c637d128 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/WeakContextAsyncTask.java @@ -0,0 +1 @@ +package code.name.monkey.retromusic.misc; import android.content.Context; import android.os.AsyncTask; import android.support.annotation.Nullable; import java.lang.ref.WeakReference; public abstract class WeakContextAsyncTask extends AsyncTask { private WeakReference contextWeakReference; public WeakContextAsyncTask(Context context) { contextWeakReference = new WeakReference<>(context); } @Nullable protected Context getContext() { return contextWeakReference.get(); } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/misc/WrappedAsyncTaskLoader.java b/app/src/main/java/code/name/monkey/retromusic/misc/WrappedAsyncTaskLoader.java new file mode 100644 index 00000000..c0345087 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/misc/WrappedAsyncTaskLoader.java @@ -0,0 +1,72 @@ + +package code.name.monkey.retromusic.misc; + +import android.content.Context; +import android.support.v4.content.AsyncTaskLoader; + +/** + * Issue + * 14944 + * + * @author Alexander Blom + */ +public abstract class WrappedAsyncTaskLoader extends AsyncTaskLoader { + + private D mData; + + /** + * Constructor of WrappedAsyncTaskLoader + * + * @param context The {@link Context} to use. + */ + public WrappedAsyncTaskLoader(Context context) { + super(context); + } + + /** + * {@inheritDoc} + */ + @Override + public void deliverResult(D data) { + if (!isReset()) { + this.mData = data; + super.deliverResult(data); + } else { + // An asynchronous query came in while the loader is stopped + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void onStartLoading() { + super.onStartLoading(); + if (this.mData != null) { + deliverResult(this.mData); + } else if (takeContentChanged() || this.mData == null) { + forceLoad(); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void onStopLoading() { + super.onStopLoading(); + // Attempt to cancel the current load task if possible + cancelLoad(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onReset() { + super.onReset(); + // Ensure the loader is stopped + onStopLoading(); + this.mData = null; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/AbsCustomPlaylist.java b/app/src/main/java/code/name/monkey/retromusic/model/AbsCustomPlaylist.java new file mode 100644 index 00000000..55ca34f7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/AbsCustomPlaylist.java @@ -0,0 +1,28 @@ +package code.name.monkey.retromusic.model; + +import android.content.Context; +import android.os.Parcel; +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import io.reactivex.Observable; + + + +public abstract class AbsCustomPlaylist extends Playlist { + public AbsCustomPlaylist(int id, String name) { + super(id, name); + } + + public AbsCustomPlaylist() { + } + + public AbsCustomPlaylist(Parcel in) { + super(in); + } + + @NonNull + public abstract Observable> getSongs(Context context); +} + diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Album.java b/app/src/main/java/code/name/monkey/retromusic/model/Album.java new file mode 100644 index 00000000..93bf4c45 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/Album.java @@ -0,0 +1,103 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import java.util.ArrayList; + + +public class Album implements Parcelable { + + public static final Creator CREATOR = new Creator() { + public Album createFromParcel(Parcel source) { + return new Album(source); + } + + public Album[] newArray(int size) { + return new Album[size]; + } + }; + public final ArrayList songs; + + public Album(ArrayList songs) { + this.songs = songs; + } + + public Album() { + this.songs = new ArrayList<>(); + } + + protected Album(Parcel in) { + this.songs = in.createTypedArrayList(Song.CREATOR); + } + + public int getId() { + return safeGetFirstSong().albumId; + } + + public String getTitle() { + return safeGetFirstSong().albumName; + } + + public int getArtistId() { + return safeGetFirstSong().artistId; + } + + public String getArtistName() { + return safeGetFirstSong().artistName; + } + + public int getYear() { + return safeGetFirstSong().year; + } + + public long getDateModified() { + return safeGetFirstSong().dateModified; + } + + public int getSongCount() { + return songs.size(); + } + + @NonNull + public Song safeGetFirstSong() { + return songs.isEmpty() ? Song.EMPTY_SONG : songs.get(0); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Album that = (Album) o; + + return songs != null ? songs.equals(that.songs) : that.songs == null; + + } + + @Override + public int hashCode() { + return songs != null ? songs.hashCode() : 0; + } + + @Override + public String toString() { + return "Album{" + + "songs=" + songs + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(songs); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Artist.java b/app/src/main/java/code/name/monkey/retromusic/model/Artist.java new file mode 100644 index 00000000..08e41e4b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/Artist.java @@ -0,0 +1,110 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.util.MusicUtil; + + +public class Artist implements Parcelable { + public static final String UNKNOWN_ARTIST_DISPLAY_NAME = "Unknown Artist"; + public final ArrayList albums; + + public Artist(ArrayList albums) { + this.albums = albums; + } + + public Artist() { + this.albums = new ArrayList<>(); + } + + public int getId() { + return safeGetFirstAlbum().getArtistId(); + } + + public String getName() { + String name = safeGetFirstAlbum().getArtistName(); + if (MusicUtil.isArtistNameUnknown(name)) { + return UNKNOWN_ARTIST_DISPLAY_NAME; + } + return name; + } + + public int getSongCount() { + int songCount = 0; + for (Album album : albums) { + songCount += album.getSongCount(); + } + return songCount; + } + + public int getAlbumCount() { + return albums.size(); + } + + public ArrayList getSongs() { + ArrayList songs = new ArrayList<>(); + for (Album album : albums) { + songs.addAll(album.songs); + } + return songs; + } + + @NonNull + public Album safeGetFirstAlbum() { + return albums.isEmpty() ? new Album() : albums.get(0); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Artist artist = (Artist) o; + + return albums != null ? albums.equals(artist.albums) : artist.albums == null; + + } + + @Override + public int hashCode() { + return albums != null ? albums.hashCode() : 0; + } + + @Override + public String toString() { + return "Artist{" + + "albums=" + albums + + '}'; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(this.albums); + } + + protected Artist(Parcel in) { + this.albums = in.createTypedArrayList(Album.CREATOR); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Artist createFromParcel(Parcel source) { + return new Artist(source); + } + + @Override + public Artist[] newArray(int size) { + return new Artist[size]; + } + }; +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.java b/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.java new file mode 100644 index 00000000..44287d37 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/CategoryInfo.java @@ -0,0 +1,56 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; + +import code.name.monkey.retromusic.R; + + +public class CategoryInfo implements Parcelable { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public CategoryInfo createFromParcel(Parcel source) { + return new CategoryInfo(source); + } + + public CategoryInfo[] newArray(int size) { + return new CategoryInfo[size]; + } + }; + public Category category; + public boolean visible; + + public CategoryInfo(Category category, boolean visible) { + this.category = category; + this.visible = visible; + } + + + private CategoryInfo(Parcel source) { + category = (Category) source.readSerializable(); + visible = source.readInt() == 1; + } + + @Override + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeSerializable(category); + dest.writeInt(visible ? 1 : 0); + } + + public enum Category { + SONGS(R.string.songs), + ALBUMS(R.string.albums), + ARTISTS(R.string.artists), + GENRES(R.string.genres), + PLAYLISTS(R.string.playlists); + + public final int stringRes; + + Category(int stringRes) { + this.stringRes = stringRes; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Genre.java b/app/src/main/java/code/name/monkey/retromusic/model/Genre.java new file mode 100644 index 00000000..9522536d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/Genre.java @@ -0,0 +1,86 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @author Hemanth S (h4h13). + */ + +public class Genre implements Parcelable { + + public static final Creator CREATOR = new Creator() { + @Override + public Genre createFromParcel(Parcel in) { + return new Genre(in); + } + + @Override + public Genre[] newArray(int size) { + return new Genre[size]; + } + }; + public final int id; + public final String name; + public final int songCount; + + public Genre(final int id, final String name, int songCount) { + this.id = id; + this.name = name; + this.songCount = songCount; + } + + + // For unknown genre + public Genre(final String name, final int songCount) { + this.id = -1; + this.name = name; + this.songCount = songCount; + } + + protected Genre(Parcel in) { + id = in.readInt(); + name = in.readString(); + songCount = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeString(name); + dest.writeInt(songCount); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Genre genre = (Genre) o; + + if (id != genre.id) return false; + return name != null ? name.equals(genre.name) : genre.name == null; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Genre{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Home.java b/app/src/main/java/code/name/monkey/retromusic/model/Home.java new file mode 100755 index 00000000..0004c32d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/Home.java @@ -0,0 +1,50 @@ +package code.name.monkey.retromusic.model; + +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; + +import java.util.ArrayList; + + +/** + * Created by BlackFootSanji on 5/22/2017. + */ + +public class Home { + public String sectionTitle; + public ArrayList list; + public AbsSmartPlaylist playlist; + + public Home(String sectionTitle, AbsSmartPlaylist playlist) { + this.sectionTitle = sectionTitle; + this.playlist = playlist; + } + + public Home(String sectionTitle, ArrayList list) { + this.sectionTitle = sectionTitle; + this.list = list; + } + + public Home(AbsSmartPlaylist playlist) { + this.playlist = playlist; + } + + public AbsSmartPlaylist getPlaylist() { + return playlist; + } + + @Override + public String toString() { + return "Home{" + + "sectionTitle='" + sectionTitle + '\'' + + ", songs=" + list + + '}'; + } + + public String getSectionTitle() { + return sectionTitle; + } + + public ArrayList getList() { + return list; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Playlist.java b/app/src/main/java/code/name/monkey/retromusic/model/Playlist.java new file mode 100644 index 00000000..a04f3126 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/Playlist.java @@ -0,0 +1,72 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; + + +public class Playlist implements Parcelable { + public static final Creator CREATOR = new Creator() { + public Playlist createFromParcel(Parcel source) { + return new Playlist(source); + } + + public Playlist[] newArray(int size) { + return new Playlist[size]; + } + }; + public final int id; + public final String name; + + public Playlist(final int id, final String name) { + this.id = id; + this.name = name; + } + + public Playlist() { + this.id = -1; + this.name = ""; + } + + protected Playlist(Parcel in) { + this.id = in.readInt(); + this.name = in.readString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Playlist playlist = (Playlist) o; + + if (id != playlist.id) return false; + return name != null ? name.equals(playlist.name) : playlist.name == null; + + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Playlist{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.id); + dest.writeString(this.name); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/PlaylistSong.java b/app/src/main/java/code/name/monkey/retromusic/model/PlaylistSong.java new file mode 100644 index 00000000..80fecdff --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/PlaylistSong.java @@ -0,0 +1,76 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; + +public class PlaylistSong extends Song { + public static final PlaylistSong EMPTY_PLAYLIST_SONG = new PlaylistSong(-1, "", -1, -1, -1, "", -1, -1, "", -1, "", -1, -1); + + public final int playlistId; + public final int idInPlayList; + + public PlaylistSong(int id, String title, int trackNumber, int year, long duration, String data, int dateModified, int albumId, String albumName, int artistId, String artistName, final int playlistId, final int idInPlayList) { + super(id, title, trackNumber, year, duration, data, dateModified, albumId, albumName, artistId, artistName); + this.playlistId = playlistId; + this.idInPlayList = idInPlayList; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + PlaylistSong that = (PlaylistSong) o; + + if (playlistId != that.playlistId) return false; + return idInPlayList == that.idInPlayList; + + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + playlistId; + result = 31 * result + idInPlayList; + return result; + } + + @Override + public String toString() { + return super.toString() + + "PlaylistSong{" + + "playlistId=" + playlistId + + ", idInPlayList=" + idInPlayList + + '}'; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(this.playlistId); + dest.writeInt(this.idInPlayList); + } + + protected PlaylistSong(Parcel in) { + super(in); + this.playlistId = in.readInt(); + this.idInPlayList = in.readInt(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public PlaylistSong createFromParcel(Parcel source) { + return new PlaylistSong(source); + } + + public PlaylistSong[] newArray(int size) { + return new PlaylistSong[size]; + } + }; +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/Song.java b/app/src/main/java/code/name/monkey/retromusic/model/Song.java new file mode 100644 index 00000000..ce7a63e1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/Song.java @@ -0,0 +1,135 @@ +package code.name.monkey.retromusic.model; + +import android.os.Parcel; +import android.os.Parcelable; + + +public class Song implements Parcelable { + public static final Song EMPTY_SONG = new Song(-1, "", -1, -1, -1, "", -1, -1, "", -1, ""); + + public final int id; + public final String title; + public final int trackNumber; + public final int year; + public final long duration; + public final String data; + public final long dateModified; + public final int albumId; + public final String albumName; + public final int artistId; + public final String artistName; + + public Song(int id, String title, int trackNumber, int year, long duration, String data, long dateModified, int albumId, String albumName, int artistId, String artistName) { + this.id = id; + this.title = title; + this.trackNumber = trackNumber; + this.year = year; + this.duration = duration; + this.data = data; + this.dateModified = dateModified; + this.albumId = albumId; + this.albumName = albumName; + this.artistId = artistId; + this.artistName = artistName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Song song = (Song) o; + + if (id != song.id) return false; + if (trackNumber != song.trackNumber) return false; + if (year != song.year) return false; + if (duration != song.duration) return false; + if (dateModified != song.dateModified) return false; + if (albumId != song.albumId) return false; + if (artistId != song.artistId) return false; + if (title != null ? !title.equals(song.title) : song.title != null) return false; + if (data != null ? !data.equals(song.data) : song.data != null) return false; + if (albumName != null ? !albumName.equals(song.albumName) : song.albumName != null) + return false; + return artistName != null ? artistName.equals(song.artistName) : song.artistName == null; + + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (title != null ? title.hashCode() : 0); + result = 31 * result + trackNumber; + result = 31 * result + year; + result = 31 * result + (int) (duration ^ (duration >>> 32)); + result = 31 * result + (data != null ? data.hashCode() : 0); + result = 31 * result + (int) (dateModified ^ (dateModified >>> 32)); + result = 31 * result + albumId; + result = 31 * result + (albumName != null ? albumName.hashCode() : 0); + result = 31 * result + artistId; + result = 31 * result + (artistName != null ? artistName.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Song{" + + "id=" + id + + ", title='" + title + '\'' + + ", trackNumber=" + trackNumber + + ", year=" + year + + ", duration=" + duration + + ", data='" + data + '\'' + + ", dateModified=" + dateModified + + ", albumId=" + albumId + + ", albumName='" + albumName + '\'' + + ", artistId=" + artistId + + ", artistName='" + artistName + '\'' + + '}'; + } + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.id); + dest.writeString(this.title); + dest.writeInt(this.trackNumber); + dest.writeInt(this.year); + dest.writeLong(this.duration); + dest.writeString(this.data); + dest.writeLong(this.dateModified); + dest.writeInt(this.albumId); + dest.writeString(this.albumName); + dest.writeInt(this.artistId); + dest.writeString(this.artistName); + } + + protected Song(Parcel in) { + this.id = in.readInt(); + this.title = in.readString(); + this.trackNumber = in.readInt(); + this.year = in.readInt(); + this.duration = in.readLong(); + this.data = in.readString(); + this.dateModified = in.readLong(); + this.albumId = in.readInt(); + this.albumName = in.readString(); + this.artistId = in.readInt(); + this.artistName = in.readString(); + } + + public static final Creator CREATOR = new Creator() { + public Song createFromParcel(Parcel source) { + return new Song(source); + } + + public Song[] newArray(int size) { + return new Song[size]; + } + }; +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/AbsSynchronizedLyrics.java b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/AbsSynchronizedLyrics.java new file mode 100644 index 00000000..2ea13f5d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/AbsSynchronizedLyrics.java @@ -0,0 +1,55 @@ +package code.name.monkey.retromusic.model.lyrics; + +import android.util.SparseArray; + +public abstract class AbsSynchronizedLyrics extends Lyrics { + private static final int TIME_OFFSET_MS = 500; // time adjustment to display line before it actually starts + + protected final SparseArray lines = new SparseArray<>(); + protected int offset = 0; + + public String getLine(int time) { + time += offset + AbsSynchronizedLyrics.TIME_OFFSET_MS; + + int lastLineTime = lines.keyAt(0); + + for (int i = 0; i < lines.size(); i++) { + int lineTime = lines.keyAt(i); + + if (time >= lineTime) { + lastLineTime = lineTime; + } else { + break; + } + } + + return lines.get(lastLineTime); + } + + public boolean isSynchronized() { + return true; + } + + public boolean isValid() { + parse(true); + return valid; + } + + @Override + public String getText() { + parse(false); + + if (valid) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < lines.size(); i++) { + String line = lines.valueAt(i); + sb.append(line).append("\r\n"); + } + + return sb.toString().trim().replaceAll("(\r?\n){3,}", "\r\n\r\n"); + } + + return super.getText(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java new file mode 100644 index 00000000..f80e5395 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/Lyrics.java @@ -0,0 +1,70 @@ +package code.name.monkey.retromusic.model.lyrics; + + +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + + +public class Lyrics { + private static final ArrayList> FORMATS = new ArrayList<>(); + + public Song song; + public String data; + + protected boolean parsed = false; + protected boolean valid = false; + + public Lyrics setData(Song song, String data) { + this.song = song; + this.data = data; + return this; + } + + public static Lyrics parse(Song song, String data) { + for (Class format : Lyrics.FORMATS) { + try { + Lyrics lyrics = format.newInstance().setData(song, data); + if (lyrics.isValid()) return lyrics.parse(false); + } catch (Exception e) { + e.printStackTrace(); + } + } + return new Lyrics().setData(song, data).parse(false); + } + + public static boolean isSynchronized(String data) { + for (Class format : Lyrics.FORMATS) { + try { + Lyrics lyrics = format.newInstance().setData(null, data); + if (lyrics.isValid()) return true; + } catch (Exception e) { + e.printStackTrace(); + } + } + return false; + } + + public Lyrics parse(boolean check) { + this.valid = true; + this.parsed = true; + return this; + } + + public boolean isSynchronized() { + return false; + } + + public boolean isValid() { + this.parse(true); + return this.valid; + } + + public String getText() { + return this.data.trim().replaceAll("(\r?\n){3,}", "\r\n\r\n"); + } + + static { + Lyrics.FORMATS.add(SynchronizedLyricsLRC.class); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/lyrics/SynchronizedLyricsLRC.java b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/SynchronizedLyricsLRC.java new file mode 100644 index 00000000..f307ae89 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/lyrics/SynchronizedLyricsLRC.java @@ -0,0 +1,72 @@ +package code.name.monkey.retromusic.model.lyrics; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class SynchronizedLyricsLRC extends AbsSynchronizedLyrics { + private static final Pattern LRC_LINE_PATTERN = Pattern.compile("((?:\\[.*?\\])+)(.*)"); + private static final Pattern LRC_TIME_PATTERN = Pattern.compile("\\[(\\d+):(\\d{2}(?:\\.\\d+)?)\\]"); + private static final Pattern LRC_ATTRIBUTE_PATTERN = Pattern.compile("\\[(\\D+):(.+)\\]"); + + private static final float LRC_SECONDS_TO_MS_MULTIPLIER = 1000f; + private static final int LRC_MINUTES_TO_MS_MULTIPLIER = 60000; + + @Override + public SynchronizedLyricsLRC parse(boolean check) { + if (this.parsed || this.data == null || this.data.isEmpty()) { + return this; + } + + String[] lines = this.data.split("\r?\n"); + + for (String line : lines) { + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + Matcher attrMatcher = SynchronizedLyricsLRC.LRC_ATTRIBUTE_PATTERN.matcher(line); + if (attrMatcher.find()) { + try { + String attr = attrMatcher.group(1).toLowerCase().trim(); + String value = attrMatcher.group(2).toLowerCase().trim(); + switch (attr) { + case "offset": + this.offset = Integer.parseInt(value); + break; + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } else { + Matcher matcher = SynchronizedLyricsLRC.LRC_LINE_PATTERN.matcher(line); + if (matcher.find()) { + String time = matcher.group(1); + String text = matcher.group(2); + + Matcher timeMatcher = SynchronizedLyricsLRC.LRC_TIME_PATTERN.matcher(time); + while (timeMatcher.find()) { + int m = 0; + float s = 0f; + try { + m = Integer.parseInt(timeMatcher.group(1)); + s = Float.parseFloat(timeMatcher.group(2)); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + } + int ms = (int) (s * LRC_SECONDS_TO_MS_MULTIPLIER) + m * LRC_MINUTES_TO_MS_MULTIPLIER; + + this.valid = true; + if (check) return this; + + this.lines.append(ms, text); + } + } + } + } + + this.parsed = true; + + return this; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/AbsSmartPlaylist.java b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/AbsSmartPlaylist.java new file mode 100644 index 00000000..c8019ad3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/AbsSmartPlaylist.java @@ -0,0 +1,63 @@ +package code.name.monkey.retromusic.model.smartplaylist; + +import android.content.Context; +import android.os.Parcel; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.AbsCustomPlaylist; + + +public abstract class AbsSmartPlaylist extends AbsCustomPlaylist { + @DrawableRes + public final int iconRes; + + public AbsSmartPlaylist(final String name, final int iconRes) { + super(-Math.abs(31 * name.hashCode() + (iconRes * name.hashCode() * 31 * 31)), name); + this.iconRes = iconRes; + } + + public AbsSmartPlaylist() { + super(); + this.iconRes = R.drawable.ic_playlist_play_white_24dp; + } + + protected AbsSmartPlaylist(Parcel in) { + super(in); + this.iconRes = in.readInt(); + } + + public abstract void clear(Context context); + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + iconRes; + return result; + } + + @Override + public boolean equals(@Nullable final Object obj) { + if (super.equals(obj)) { + if (getClass() != obj.getClass()) { + return false; + } + final AbsSmartPlaylist other = (AbsSmartPlaylist) obj; + return iconRes == other.iconRes; + } + return false; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(this.iconRes); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/HistoryPlaylist.java b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/HistoryPlaylist.java new file mode 100644 index 00000000..abec592d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/HistoryPlaylist.java @@ -0,0 +1,57 @@ +package code.name.monkey.retromusic.model.smartplaylist; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.providers.HistoryStore; +import io.reactivex.Observable; + + +public class HistoryPlaylist extends AbsSmartPlaylist { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public HistoryPlaylist createFromParcel(Parcel source) { + return new HistoryPlaylist(source); + } + + public HistoryPlaylist[] newArray(int size) { + return new HistoryPlaylist[size]; + } + }; + + public HistoryPlaylist(@NonNull Context context) { + super(context.getString(R.string.history), R.drawable.ic_access_time_white_24dp); + } + + protected HistoryPlaylist(Parcel in) { + super(in); + } + + @NonNull + @Override + public Observable> getSongs(@NonNull Context context) { + return TopAndRecentlyPlayedTracksLoader.getRecentlyPlayedTracks(context); + } + + @Override + public void clear(@NonNull Context context) { + HistoryStore.getInstance(context).clear(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/LastAddedPlaylist.java b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/LastAddedPlaylist.java new file mode 100644 index 00000000..3e726d63 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/LastAddedPlaylist.java @@ -0,0 +1,57 @@ +package code.name.monkey.retromusic.model.smartplaylist; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.loaders.LastAddedSongsLoader; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import io.reactivex.Observable; + + +public class LastAddedPlaylist extends AbsSmartPlaylist { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public LastAddedPlaylist createFromParcel(Parcel source) { + return new LastAddedPlaylist(source); + } + + public LastAddedPlaylist[] newArray(int size) { + return new LastAddedPlaylist[size]; + } + }; + + public LastAddedPlaylist(@NonNull Context context) { + super(context.getString(R.string.last_added), R.drawable.ic_library_add_white_24dp); + } + + protected LastAddedPlaylist(Parcel in) { + super(in); + } + + @NonNull + @Override + public Observable> getSongs(@NonNull Context context) { + return LastAddedSongsLoader.getLastAddedSongs(context); + } + + @Override + public void clear(@NonNull Context context) { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/MyTopTracksPlaylist.java b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/MyTopTracksPlaylist.java new file mode 100644 index 00000000..2decf3f7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/MyTopTracksPlaylist.java @@ -0,0 +1,59 @@ +package code.name.monkey.retromusic.model.smartplaylist; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.providers.SongPlayCountStore; + +import java.util.ArrayList; + +import io.reactivex.Observable; + + +public class MyTopTracksPlaylist extends AbsSmartPlaylist implements Parcelable { + public static final Creator CREATOR = new Creator() { + public MyTopTracksPlaylist createFromParcel(Parcel source) { + return new MyTopTracksPlaylist(source); + } + + public MyTopTracksPlaylist[] newArray(int size) { + return new MyTopTracksPlaylist[size]; + } + }; + + public MyTopTracksPlaylist(@NonNull Context context) { + super(context.getString(R.string.my_top_tracks), R.drawable.ic_trending_up_white_24dp); + } + + protected MyTopTracksPlaylist(Parcel in) { + super(in); + } + + @NonNull + @Override + public Observable> getSongs(@NonNull Context context) { + return TopAndRecentlyPlayedTracksLoader.getTopTracks(context); + } + + @Override + public void clear(@NonNull Context context) { + SongPlayCountStore.getInstance(context).clear(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/ShuffleAllPlaylist.java b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/ShuffleAllPlaylist.java new file mode 100644 index 00000000..fdada0c5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/model/smartplaylist/ShuffleAllPlaylist.java @@ -0,0 +1,57 @@ +package code.name.monkey.retromusic.model.smartplaylist; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import io.reactivex.Observable; + +public class ShuffleAllPlaylist extends AbsSmartPlaylist { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ShuffleAllPlaylist createFromParcel(Parcel source) { + return new ShuffleAllPlaylist(source); + } + + public ShuffleAllPlaylist[] newArray(int size) { + return new ShuffleAllPlaylist[size]; + } + }; + + public ShuffleAllPlaylist(@NonNull Context context) { + super(context.getString(R.string.action_shuffle_all), R.drawable.ic_shuffle_white_24dp); + } + + protected ShuffleAllPlaylist(Parcel in) { + super(in); + } + + @NonNull + @Override + public Observable> getSongs(@NonNull Context context) { + return SongLoader.getAllSongs(context); + } + + @Override + public void clear(@NonNull Context context) { + // Shuffle all is not a real "Smart Playlist" + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/BasePresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/BasePresenter.java new file mode 100644 index 00000000..68a4e9f4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/BasePresenter.java @@ -0,0 +1,12 @@ +package code.name.monkey.retromusic.mvp; + +/** + * Created by hemanths on 09/08/17. + */ + +public interface BasePresenter { + + void subscribe(); + + void unsubscribe(); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/BaseView.java b/app/src/main/java/code/name/monkey/retromusic/mvp/BaseView.java new file mode 100644 index 00000000..70f08539 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/BaseView.java @@ -0,0 +1,15 @@ +package code.name.monkey.retromusic.mvp; + +/** + * Created by hemanths on 09/08/17. + */ + +public interface BaseView { + void loading(); + + void showData(T list); + + void showEmptyView(); + + void completed(); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/Presenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/Presenter.java new file mode 100644 index 00000000..73900b36 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/Presenter.java @@ -0,0 +1,27 @@ +package code.name.monkey.retromusic.mvp; + +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.Injection; +import code.name.monkey.retromusic.providers.interfaces.Repository; +import code.name.monkey.retromusic.util.schedulers.BaseSchedulerProvider; +import io.reactivex.disposables.CompositeDisposable; + +/** + * Created by hemanths on 16/08/17. + */ + +public class Presenter { + @NonNull + protected Repository repository; + @NonNull + protected CompositeDisposable disposable; + @NonNull + protected BaseSchedulerProvider schedulerProvider; + + public Presenter() { + this.repository = Injection.provideRepository(); + this.schedulerProvider = Injection.provideSchedulerProvider(); + this.disposable = new CompositeDisposable(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/AlbumContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/AlbumContract.java new file mode 100644 index 00000000..ae3a8838 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/AlbumContract.java @@ -0,0 +1,19 @@ +package code.name.monkey.retromusic.mvp.contract; + +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; +import java.util.ArrayList; + +public interface AlbumContract { + + interface AlbumView extends BaseView> { + + } + + interface Presenter extends BasePresenter { + + void loadAlbums(); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/AlbumDetailsContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/AlbumDetailsContract.java new file mode 100644 index 00000000..d22d5b0a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/AlbumDetailsContract.java @@ -0,0 +1,18 @@ +package code.name.monkey.retromusic.mvp.contract; + + +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +public interface AlbumDetailsContract { + + interface AlbumDetailsView extends BaseView { + + } + + interface Presenter extends BasePresenter { + + void loadAlbumSongs(int albumId); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/ArtistContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/ArtistContract.java new file mode 100644 index 00000000..b9d179c3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/ArtistContract.java @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic.mvp.contract; + + +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + + +/** + * Created by hemanths on 16/08/17. + */ + +public interface ArtistContract { + interface ArtistView extends BaseView> { + + } + + interface Presenter extends BasePresenter { + void loadArtists(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/ArtistDetailContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/ArtistDetailContract.java new file mode 100644 index 00000000..2f8347b2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/ArtistDetailContract.java @@ -0,0 +1,21 @@ +package code.name.monkey.retromusic.mvp.contract; + + +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + + +/** + * Created by hemanths on 20/08/17. + */ + +public interface ArtistDetailContract { + interface ArtistsDetailsView extends BaseView { + + } + + interface Presenter extends BasePresenter { + void loadArtistById(int artistId); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/GenreContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/GenreContract.java new file mode 100644 index 00000000..d47d14f9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/GenreContract.java @@ -0,0 +1,21 @@ +package code.name.monkey.retromusic.mvp.contract; + +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + +/** + * @author Hemanth S (h4h13). + */ + +public interface GenreContract { + interface GenreView extends BaseView> { + + } + + interface Presenter extends BasePresenter { + void loadGenre(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/GenreDetailsContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/GenreDetailsContract.java new file mode 100644 index 00000000..7eeb5b6a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/GenreDetailsContract.java @@ -0,0 +1,20 @@ +package code.name.monkey.retromusic.mvp.contract; + +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + +/** + * @author Hemanth S (h4h13). + */ + +public interface GenreDetailsContract { + interface GenreDetailsView extends BaseView> { + } + + interface Presenter extends BasePresenter { + void loadGenre(int genreId); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/HomeContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/HomeContract.java new file mode 100644 index 00000000..892b0d57 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/HomeContract.java @@ -0,0 +1,44 @@ +package code.name.monkey.retromusic.mvp.contract; + +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + +public interface HomeContract { + + interface HomeView extends BaseView> { + + void recentArtist(ArrayList artists); + + void recentAlbum(ArrayList albums); + + void topArtists(ArrayList artists); + + void topAlbums(ArrayList albums); + + void suggestions(ArrayList songs); + + void geners(ArrayList songs); + } + + interface HomePresenter extends BasePresenter { + + void loadRecentAlbums(); + + void loadTopAlbums(); + + void loadRecentArtists(); + + void loadTopArtists(); + + void loadSuggestions(); + + void loadGenres(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/PlaylistContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/PlaylistContract.java new file mode 100644 index 00000000..7a40a5e5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/PlaylistContract.java @@ -0,0 +1,21 @@ +package code.name.monkey.retromusic.mvp.contract; + +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + +/** + * Created by hemanths on 19/08/17. + */ + +public interface PlaylistContract { + interface PlaylistView extends BaseView > { + + } + + interface Presenter extends BasePresenter { + void loadPlaylists(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/PlaylistSongsContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/PlaylistSongsContract.java new file mode 100644 index 00000000..48639a8f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/PlaylistSongsContract.java @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic.mvp.contract; + +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + + +/** + * Created by hemanths on 20/08/17. + */ + +public interface PlaylistSongsContract { + interface PlaylistSongsView extends BaseView> { + + } + + interface Presenter extends BasePresenter { + void loadSongs(Playlist playlist); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/SearchContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/SearchContract.java new file mode 100644 index 00000000..2684894e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/SearchContract.java @@ -0,0 +1,21 @@ +package code.name.monkey.retromusic.mvp.contract; + +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + + +/** + * Created by hemanths on 20/08/17. + */ + +public interface SearchContract { + interface SearchView extends BaseView> { + + } + + interface SearchPresenter extends BasePresenter { + void search(String query); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/contract/SongContract.java b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/SongContract.java new file mode 100644 index 00000000..2ec627e0 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/contract/SongContract.java @@ -0,0 +1,24 @@ +package code.name.monkey.retromusic.mvp.contract; + + +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.BasePresenter; +import code.name.monkey.retromusic.mvp.BaseView; + +import java.util.ArrayList; + + +/** + * Created by hemanths on 10/08/17. + */ + +public interface SongContract { + + interface SongView extends BaseView> { + + } + + interface Presenter extends BasePresenter { + void loadSongs(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/AlbumDetailsPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/AlbumDetailsPresenter.java new file mode 100644 index 00000000..053eef11 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/AlbumDetailsPresenter.java @@ -0,0 +1,54 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.AlbumDetailsContract; + + +/** + * Created by hemanths on 20/08/17. + */ + +public class AlbumDetailsPresenter extends Presenter implements AlbumDetailsContract.Presenter { + + @NonNull + private final int albumId; + @NonNull + private AlbumDetailsContract.AlbumDetailsView view; + + public AlbumDetailsPresenter(@NonNull AlbumDetailsContract.AlbumDetailsView view, int albumId) { + + this.view = view; + this.albumId = albumId; + } + + @Override + public void subscribe() { + loadAlbumSongs(albumId); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + @Override + public void loadAlbumSongs(int albumId) { + disposable.add(repository.getAlbum(albumId) + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(this::showAlbum, + throwable -> view.showEmptyView(), + () -> view.completed())); + } + + private void showAlbum(Album album) { + if (album != null) { + view.showData(album); + } else { + view.showEmptyView(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/AlbumPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/AlbumPresenter.java new file mode 100644 index 00000000..46bbc341 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/AlbumPresenter.java @@ -0,0 +1,49 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.AlbumContract; + + +/** + * Created by hemanths on 12/08/17. + */ + +public class AlbumPresenter extends Presenter implements AlbumContract.Presenter { + @NonNull + private AlbumContract.AlbumView view; + + + public AlbumPresenter(@NonNull AlbumContract.AlbumView view) { + this.view = view; + } + + @Override + public void subscribe() { + loadAlbums(); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + private void showList(@NonNull ArrayList albums) { + view.showData(albums); + } + + @Override + public void loadAlbums() { + disposable.add(repository.getAllAlbums() + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(this::showList, + throwable -> view.showEmptyView(), + () -> view.completed())); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistDetailsPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistDetailsPresenter.java new file mode 100644 index 00000000..22fcc390 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistDetailsPresenter.java @@ -0,0 +1,54 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.ArtistDetailContract; + + +/** + * Created by hemanths on 20/08/17. + */ + +public class ArtistDetailsPresenter extends Presenter implements ArtistDetailContract.Presenter { + + private final int artistId; + @NonNull + private final ArtistDetailContract.ArtistsDetailsView view; + + public ArtistDetailsPresenter(@NonNull ArtistDetailContract.ArtistsDetailsView view, + int artistId) { + this.view = view; + this.artistId = artistId; + } + + @Override + public void subscribe() { + loadArtistById(artistId); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + @Override + public void loadArtistById(int artistId) { + disposable.add(repository.getArtistById(artistId) + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(this::showArtist, + throwable -> view.showEmptyView(), + () -> view.completed())); + } + + private void showArtist(Artist album) { + if (album != null) { + view.showData(album); + } else { + view.showEmptyView(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistPresenter.java new file mode 100644 index 00000000..613e201f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/ArtistPresenter.java @@ -0,0 +1,47 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.ArtistContract; + +public class ArtistPresenter extends Presenter implements ArtistContract.Presenter { + @NonNull + private ArtistContract.ArtistView mView; + + public ArtistPresenter(@NonNull ArtistContract.ArtistView view) { + mView = view; + } + + @Override + public void subscribe() { + loadArtists(); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + private void showList(@NonNull ArrayList songs) { + if (songs.isEmpty()) { + mView.showEmptyView(); + } else { + mView.showData(songs); + } + } + + @Override + public void loadArtists() { + disposable.add(repository.getAllArtists() + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> mView.loading()) + .subscribe(this::showList, + throwable -> mView.showEmptyView(), + () -> mView.completed())); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/GenreDetailsPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/GenreDetailsPresenter.java new file mode 100644 index 00000000..0fd3e200 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/GenreDetailsPresenter.java @@ -0,0 +1,56 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.GenreDetailsContract; + + +/** + * Created by hemanths on 20/08/17. + */ + +public class GenreDetailsPresenter extends Presenter + implements GenreDetailsContract.Presenter { + private final int genreId; + @NonNull + private GenreDetailsContract.GenreDetailsView view; + + public GenreDetailsPresenter(@NonNull GenreDetailsContract.GenreDetailsView view, + int genreId) { + this.view = view; + this.genreId = genreId; + } + + @Override + public void subscribe() { + loadGenre(genreId); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + @Override + public void loadGenre(int genreId) { + disposable.add(repository.getGenre(genreId) + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(this::showGenre, + throwable -> view.showEmptyView(), + () -> view.completed())); + } + + private void showGenre(ArrayList songs) { + if (songs != null) { + view.showData(songs); + } else { + view.showEmptyView(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/GenrePresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/GenrePresenter.java new file mode 100644 index 00000000..cd9d9fa2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/GenrePresenter.java @@ -0,0 +1,53 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.GenreContract; + +/** + * @author Hemanth S (h4h13). + */ + +public class GenrePresenter extends Presenter + implements GenreContract.Presenter { + @NonNull + private GenreContract.GenreView view; + + public GenrePresenter( + @NonNull GenreContract.GenreView view) { + this.view = view; + } + + @Override + public void subscribe() { + loadGenre(); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + @Override + public void loadGenre() { + disposable.add(repository.getAllGenres() + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(this::showList, + throwable -> view.showEmptyView(), + () -> view.completed())); + } + + private void showList(@NonNull ArrayList genres) { + if (genres.isEmpty()) { + view.showEmptyView(); + } else { + view.showData(genres); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/HomePresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/HomePresenter.java new file mode 100644 index 00000000..3511872d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/HomePresenter.java @@ -0,0 +1,128 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.HomeContract; +import code.name.monkey.retromusic.util.PreferenceUtil; + +public class HomePresenter extends Presenter implements HomeContract.HomePresenter { + + @NonNull + private HomeContract.HomeView view; + + public HomePresenter(@NonNull HomeContract.HomeView view) { + this.view = view; + } + + @Override + public void subscribe() { + + loadRecentAlbums(); + loadRecentArtists(); + + loadTopAlbums(); + loadTopArtists(); + + loadSuggestions(); + + if (PreferenceUtil.getInstance(RetroApplication.getInstance()).isGenreShown()) { + loadGenres(); + } + + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + public void loadPlaylists() { + disposable.add(repository.getAllPlaylists() + .observeOn(schedulerProvider.ui()) + .subscribeOn(schedulerProvider.io()) + .subscribe(playlists -> { + if (!playlists.isEmpty()) { + view.suggestions(playlists); + } + }, + throwable -> view.showEmptyView(), () -> view.completed())); + } + + @Override + public void loadRecentAlbums() { + disposable.add(repository.getRecentAlbums() + .observeOn(schedulerProvider.ui()) + .subscribeOn(schedulerProvider.io()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(artists -> { + if (!artists.isEmpty()) { + view.recentAlbum(artists); + } + }, + throwable -> view.showEmptyView(), () -> view.completed())); + } + + @Override + public void loadTopAlbums() { + disposable.add(repository.getTopAlbums() + .observeOn(schedulerProvider.ui()) + .subscribeOn(schedulerProvider.io()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(artists -> { + if (!artists.isEmpty()) { + view.topAlbums(artists); + } + }, + throwable -> view.showEmptyView(), () -> view.completed())); + } + + @Override + public void loadRecentArtists() { + disposable.add(repository.getRecentArtists() + .observeOn(schedulerProvider.ui()) + .subscribeOn(schedulerProvider.io()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(artists -> { + if (!artists.isEmpty()) { + view.recentArtist(artists); + } + }, + throwable -> view.showEmptyView(), () -> view.completed())); + } + + @Override + public void loadTopArtists() { + disposable.add(repository.getTopArtists() + .observeOn(schedulerProvider.ui()) + .subscribeOn(schedulerProvider.io()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(artists -> { + if (!artists.isEmpty()) { + view.topArtists(artists); + } + }, + throwable -> view.showEmptyView(), () -> view.completed())); + + } + + @Override + public void loadSuggestions() { + + } + + @Override + public void loadGenres() { + disposable.add(repository.getAllGenres() + .observeOn(schedulerProvider.ui()) + .subscribeOn(schedulerProvider.io()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(genres -> { + if (!genres.isEmpty()) { + view.geners(genres); + } + }, + throwable -> view.showEmptyView(), () -> view.completed())); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/PlaylistPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/PlaylistPresenter.java new file mode 100644 index 00000000..744a055c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/PlaylistPresenter.java @@ -0,0 +1,54 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.PlaylistContract; + + +/** + * Created by hemanths on 19/08/17. + */ + +public class PlaylistPresenter extends Presenter + implements PlaylistContract.Presenter { + @NonNull + private PlaylistContract.PlaylistView mView; + + public PlaylistPresenter(@NonNull PlaylistContract.PlaylistView view) { + + mView = view; + } + + @Override + public void subscribe() { + loadPlaylists(); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + @Override + public void loadPlaylists() { + disposable.add(repository.getAllPlaylists() + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> mView.loading()) + .subscribe(this::showList, + throwable -> mView.showEmptyView(), + () -> mView.completed())); + } + + private void showList(@NonNull ArrayList songs) { + if (songs.isEmpty()) { + mView.showEmptyView(); + } else { + mView.showData(songs); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/PlaylistSongsPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/PlaylistSongsPresenter.java new file mode 100644 index 00000000..66b129df --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/PlaylistSongsPresenter.java @@ -0,0 +1,48 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.PlaylistSongsContract; + +/** + * Created by hemanths on 20/08/17. + */ + +public class PlaylistSongsPresenter extends Presenter + implements PlaylistSongsContract.Presenter { + @NonNull + private PlaylistSongsContract.PlaylistSongsView mView; + @NonNull + private Playlist mPlaylist; + + public PlaylistSongsPresenter(@NonNull PlaylistSongsContract.PlaylistSongsView view, + @NonNull Playlist playlist) { + + mView = view; + mPlaylist = playlist; + } + + + @Override + public void subscribe() { + loadSongs(mPlaylist); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + @Override + public void loadSongs(Playlist playlist) { + disposable.add(repository.getPlaylistSongs(playlist) + .subscribeOn(schedulerProvider.io()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> mView.loading()) + .subscribe(songs -> mView.showData(songs), + throwable -> mView.showEmptyView(), + () -> mView.completed())); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/SearchPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/SearchPresenter.java new file mode 100644 index 00000000..21c012ba --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/SearchPresenter.java @@ -0,0 +1,52 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.SearchContract; + +/** + * Created by hemanths on 20/08/17. + */ + +public class SearchPresenter extends Presenter implements SearchContract.SearchPresenter { + @NonNull + private SearchContract.SearchView mView; + + public SearchPresenter(@NonNull SearchContract.SearchView view) { + mView = view; + } + + @Override + public void subscribe() { + search(""); + } + + @Override + public void unsubscribe() { + disposable.clear(); + } + + private void showList(@NonNull ArrayList albums) { + if (albums.isEmpty()) { + mView.showEmptyView(); + } else { + mView.showData(albums); + } + } + + @Override + public void search(String query) { + disposable.add(repository.search(query) + .debounce(500, TimeUnit.MILLISECONDS) + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> mView.loading()) + .subscribe(this::showList, + throwable -> mView.showEmptyView(), + () -> mView.completed())); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/SongPresenter.java b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/SongPresenter.java new file mode 100644 index 00000000..d8098958 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/mvp/presenter/SongPresenter.java @@ -0,0 +1,53 @@ +package code.name.monkey.retromusic.mvp.presenter; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.Presenter; +import code.name.monkey.retromusic.mvp.contract.SongContract; + +/** + * Created by hemanths on 10/08/17. + */ + +public class SongPresenter extends Presenter implements SongContract.Presenter { + + @NonNull + private SongContract.SongView view; + + + public SongPresenter(@NonNull SongContract.SongView view) { + this.view = view; + } + + @Override + public void loadSongs() { + disposable.add(repository.getAllSongs() + .subscribeOn(schedulerProvider.computation()) + .observeOn(schedulerProvider.ui()) + .doOnSubscribe(disposable1 -> view.loading()) + .subscribe(this::showList, + throwable -> view.showEmptyView(), + () -> view.completed())); + } + + @Override + public void subscribe() { + loadSongs(); + } + + private void showList(@NonNull ArrayList songs) { + if (songs.isEmpty()) { + view.showEmptyView(); + } else { + view.showData(songs); + } + } + + @Override + public void unsubscribe() { + disposable.clear(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/BlacklistPreference.java b/app/src/main/java/code/name/monkey/retromusic/preferences/BlacklistPreference.java new file mode 100644 index 00000000..cc6e3e69 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/BlacklistPreference.java @@ -0,0 +1,25 @@ +package code.name.monkey.retromusic.preferences; + +import android.content.Context; +import android.util.AttributeSet; + +import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEDialogPreference; + + +public class BlacklistPreference extends ATEDialogPreference { + public BlacklistPreference(Context context) { + super(context); + } + + public BlacklistPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BlacklistPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public BlacklistPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/BlacklistPreferenceDialog.java b/app/src/main/java/code/name/monkey/retromusic/preferences/BlacklistPreferenceDialog.java new file mode 100644 index 00000000..9f0a560a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/BlacklistPreferenceDialog.java @@ -0,0 +1,124 @@ +package code.name.monkey.retromusic.preferences; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.text.Html; +import android.view.View; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.BlacklistFolderChooserDialog; +import code.name.monkey.retromusic.providers.BlacklistStore; +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import java.io.File; +import java.util.ArrayList; + + +public class BlacklistPreferenceDialog extends DialogFragment implements + BlacklistFolderChooserDialog.FolderCallback { + + public static final String TAG = BlacklistPreferenceDialog.class.getSimpleName(); + + private ArrayList paths; + + public static BlacklistPreferenceDialog newInstance() { + return new BlacklistPreferenceDialog(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + BlacklistFolderChooserDialog blacklistFolderChooserDialog = (BlacklistFolderChooserDialog) getChildFragmentManager() + .findFragmentByTag("FOLDER_CHOOSER"); + if (blacklistFolderChooserDialog != null) { + blacklistFolderChooserDialog.setCallback(this); + } + + refreshBlacklistData(); + return new MaterialDialog.Builder(getContext()) + .title(R.string.blacklist) + .positiveText(android.R.string.ok) + .neutralText(R.string.clear_action) + .negativeText(R.string.add_action) + .items(paths) + .autoDismiss(false) + .itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog materialDialog, View view, int i, + final CharSequence charSequence) { + new MaterialDialog.Builder(getContext()) + .title(R.string.remove_from_blacklist) + .content(Html.fromHtml( + getString(R.string.do_you_want_to_remove_from_the_blacklist, charSequence))) + .positiveText(R.string.remove_action) + .negativeText(android.R.string.cancel) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog materialDialog, + @NonNull DialogAction dialogAction) { + BlacklistStore.getInstance(getContext()) + .removePath(new File(charSequence.toString())); + refreshBlacklistData(); + } + }).show(); + } + }) + // clear + .onNeutral(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog materialDialog, + @NonNull DialogAction dialogAction) { + new MaterialDialog.Builder(getContext()) + .title(R.string.clear_blacklist) + .content(R.string.do_you_want_to_clear_the_blacklist) + .positiveText(R.string.clear_action) + .negativeText(android.R.string.cancel) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog materialDialog, + @NonNull DialogAction dialogAction) { + BlacklistStore.getInstance(getContext()).clear(); + refreshBlacklistData(); + } + }).show(); + } + }) + // add + .onNegative(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog materialDialog, + @NonNull DialogAction dialogAction) { + BlacklistFolderChooserDialog dialog = BlacklistFolderChooserDialog.create(); + dialog.setCallback(BlacklistPreferenceDialog.this); + dialog.show(getChildFragmentManager(), "FOLDER_CHOOSER"); + } + }) + .onPositive(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog materialDialog, + @NonNull DialogAction dialogAction) { + dismiss(); + } + }) + .build(); + } + + private void refreshBlacklistData() { + paths = BlacklistStore.getInstance(getContext()).getPaths(); + + MaterialDialog dialog = (MaterialDialog) getDialog(); + if (dialog != null) { + String[] pathArray = new String[paths.size()]; + pathArray = paths.toArray(pathArray); + dialog.setItems((CharSequence[]) pathArray); + } + } + + @Override + public void onFolderSelection(@NonNull BlacklistFolderChooserDialog folderChooserDialog, + @NonNull File file) { + BlacklistStore.getInstance(getContext()).addPath(file); + refreshBlacklistData(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreference.java b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreference.java new file mode 100644 index 00000000..a19e22f9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreference.java @@ -0,0 +1,25 @@ +package code.name.monkey.retromusic.preferences; + +import android.content.Context; +import android.util.AttributeSet; + +import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEDialogPreference; + + +public class NowPlayingScreenPreference extends ATEDialogPreference { + public NowPlayingScreenPreference(Context context) { + super(context); + } + + public NowPlayingScreenPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NowPlayingScreenPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public NowPlayingScreenPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.java b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.java new file mode 100644 index 00000000..253901f1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/preferences/NowPlayingScreenPreferenceDialog.java @@ -0,0 +1,165 @@ +package code.name.monkey.retromusic.preferences; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.bumptech.glide.Glide; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.ui.fragments.NowPlayingScreen; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.ViewUtil; + + +public class NowPlayingScreenPreferenceDialog extends DialogFragment implements + ViewPager.OnPageChangeListener, MaterialDialog.SingleButtonCallback { + public static final String TAG = NowPlayingScreenPreferenceDialog.class.getSimpleName(); + + private DialogAction whichButtonClicked; + private int viewPagerPosition; + + public static NowPlayingScreenPreferenceDialog newInstance() { + return new NowPlayingScreenPreferenceDialog(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + @SuppressLint("InflateParams") View view = LayoutInflater.from(getActivity()) + .inflate(R.layout.preference_dialog_now_playing_screen, null); + ViewPager viewPager = view.findViewById(R.id.now_playing_screen_view_pager); + viewPager.setAdapter(new NowPlayingScreenAdapter(getActivity())); + viewPager.addOnPageChangeListener(this); + viewPager.setPageMargin((int) ViewUtil.convertDpToPixel(32, getResources())); + viewPager.setCurrentItem(PreferenceUtil.getInstance(getActivity()).getNowPlayingScreen().ordinal()); + + return new MaterialDialog.Builder(getActivity()) + .title(R.string.pref_title_now_playing_screen_appearance) + .positiveText(android.R.string.ok) + .negativeText(android.R.string.cancel) + .onAny(this) + .customView(view, false) + .build(); + } + + @Override + public void onClick(@NonNull MaterialDialog dialog, + @NonNull DialogAction which) { + whichButtonClicked = which; + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + if (whichButtonClicked == DialogAction.POSITIVE) { + NowPlayingScreen nowPlayingScreen = NowPlayingScreen.values()[viewPagerPosition]; + if (isNowPlayingThemes(nowPlayingScreen)) { + String result = getString(nowPlayingScreen.titleRes) + " theme is Pro version feature."; + Toast.makeText(getContext(), result, Toast.LENGTH_SHORT).show(); + NavigationUtil.goToProVersion(getActivity()); + } else { + PreferenceUtil.getInstance(getActivity()).setNowPlayingScreen(nowPlayingScreen); + } + } + } + + private boolean isNowPlayingThemes(NowPlayingScreen nowPlayingScreen) { + + if (nowPlayingScreen.equals(NowPlayingScreen.BLUR_CARD)) { + PreferenceUtil.getInstance(getContext()).resetCarouselEffect(); + PreferenceUtil.getInstance(getContext()).resetCircularAlbumArt(); + } + + return (nowPlayingScreen.equals(NowPlayingScreen.FULL) || + nowPlayingScreen.equals(NowPlayingScreen.CARD) || + nowPlayingScreen.equals(NowPlayingScreen.PLAIN) || + nowPlayingScreen.equals(NowPlayingScreen.BLUR) || + nowPlayingScreen.equals(NowPlayingScreen.COLOR) || + nowPlayingScreen.equals(NowPlayingScreen.SIMPLE) || + nowPlayingScreen.equals(NowPlayingScreen.TINY) || + nowPlayingScreen.equals(NowPlayingScreen.BLUR_CARD)|| + nowPlayingScreen.equals(NowPlayingScreen.ADAPTIVE)) + && !RetroApplication.isProVersion(); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + this.viewPagerPosition = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + + private static class NowPlayingScreenAdapter extends PagerAdapter { + + private Context context; + + NowPlayingScreenAdapter(Context context) { + this.context = context; + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup collection, int position) { + NowPlayingScreen nowPlayingScreen = NowPlayingScreen.values()[position]; + + LayoutInflater inflater = LayoutInflater.from(context); + ViewGroup layout = (ViewGroup) inflater.inflate(R.layout.preference_now_playing_screen_item, collection, false); + collection.addView(layout); + + ImageView image = layout.findViewById(R.id.image); + TextView title = layout.findViewById(R.id.title); + Glide.with(context).load(nowPlayingScreen.drawableResId).into(image); + title.setText(nowPlayingScreen.titleRes); + + return layout; + } + + @Override + public void destroyItem(@NonNull ViewGroup collection, + int position, + @NonNull Object view) { + collection.removeView((View) view); + } + + @Override + public int getCount() { + return NowPlayingScreen.values().length; + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { + return view == object; + } + + @Override + public CharSequence getPageTitle(int position) { + return context.getString(NowPlayingScreen.values()[position].titleRes); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java new file mode 100644 index 00000000..2382ff8a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/providers/BlacklistStore.java @@ -0,0 +1,155 @@ +package code.name.monkey.retromusic.providers; + +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; +import android.support.annotation.NonNull; + +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.util.FileUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + +import java.io.File; +import java.util.ArrayList; + +public class BlacklistStore extends SQLiteOpenHelper { + public static final String DATABASE_NAME = "blacklist.db"; + private static final int VERSION = 1; + private static BlacklistStore sInstance = null; + private Context context; + + public BlacklistStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + this.context = context; + } + + @NonNull + public static synchronized BlacklistStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new BlacklistStore(context.getApplicationContext()); + if (!PreferenceUtil.getInstance(context).initializedBlacklist()) { + // blacklisted by default + sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS)); + sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS)); + sInstance.addPathImpl(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES)); + + PreferenceUtil.getInstance(context).setInitializedBlacklist(); + } + } + return sInstance; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + BlacklistStoreColumns.NAME + " (" + + BlacklistStoreColumns.PATH + " STRING NOT NULL);"); + } + + @Override + public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME); + onCreate(db); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + BlacklistStoreColumns.NAME); + onCreate(db); + } + + public void addPath(File file) { + addPathImpl(file); + notifyMediaStoreChanged(); + } + + private void addPathImpl(File file) { + if (file == null || contains(file)) { + return; + } + String path = FileUtil.safeGetCanonicalPath(file); + + final SQLiteDatabase database = getWritableDatabase(); + database.beginTransaction(); + + try { + // add the entry + final ContentValues values = new ContentValues(1); + values.put(BlacklistStoreColumns.PATH, path); + database.insert(BlacklistStoreColumns.NAME, null, values); + + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + } + + public boolean contains(File file) { + if (file == null) { + return false; + } + String path = FileUtil.safeGetCanonicalPath(file); + + final SQLiteDatabase database = getReadableDatabase(); + Cursor cursor = database.query(BlacklistStoreColumns.NAME, + new String[]{BlacklistStoreColumns.PATH}, + BlacklistStoreColumns.PATH + "=?", + new String[]{path}, + null, null, null, null); + + boolean containsPath = cursor != null && cursor.moveToFirst(); + if (cursor != null) { + cursor.close(); + } + return containsPath; + } + + public void removePath(File file) { + final SQLiteDatabase database = getWritableDatabase(); + String path = FileUtil.safeGetCanonicalPath(file); + + database.delete(BlacklistStoreColumns.NAME, + BlacklistStoreColumns.PATH + "=?", + new String[]{path}); + + notifyMediaStoreChanged(); + } + + public void clear() { + final SQLiteDatabase database = getWritableDatabase(); + database.delete(BlacklistStoreColumns.NAME, null, null); + + notifyMediaStoreChanged(); + } + + private void notifyMediaStoreChanged() { + context.sendBroadcast(new Intent(Constants.MEDIA_STORE_CHANGED)); + } + + @NonNull + public ArrayList getPaths() { + Cursor cursor = getReadableDatabase().query(BlacklistStoreColumns.NAME, + new String[]{BlacklistStoreColumns.PATH}, + null, null, null, null, null); + + ArrayList paths = new ArrayList<>(); + if (cursor != null && cursor.moveToFirst()) { + do { + paths.add(cursor.getString(0)); + } while (cursor.moveToNext()); + } + + if (cursor != null) + cursor.close(); + return paths; + } + + public interface BlacklistStoreColumns { + String NAME = "blacklist"; + + String PATH = "path"; + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java new file mode 100644 index 00000000..dfb4c511 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/providers/HistoryStore.java @@ -0,0 +1,152 @@ +/* +* Copyright (C) 2014 The CyanogenMod Project +* +* 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 code.name.monkey.retromusic.providers; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class HistoryStore extends SQLiteOpenHelper { + public static final String DATABASE_NAME = "history.db"; + private static final int MAX_ITEMS_IN_DB = 100; + private static final int VERSION = 1; + @Nullable + private static HistoryStore sInstance = null; + + public HistoryStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + } + + @NonNull + public static synchronized HistoryStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new HistoryStore(context.getApplicationContext()); + } + return sInstance; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + RecentStoreColumns.NAME + " (" + + RecentStoreColumns.ID + " LONG NOT NULL," + RecentStoreColumns.TIME_PLAYED + + " LONG NOT NULL);"); + } + + @Override + public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME); + onCreate(db); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + RecentStoreColumns.NAME); + onCreate(db); + } + + public void addSongId(final long songId) { + if (songId == -1) { + return; + } + + final SQLiteDatabase database = getWritableDatabase(); + database.beginTransaction(); + + try { + // remove previous entries + removeSongId(songId); + + // add the entry + final ContentValues values = new ContentValues(2); + values.put(RecentStoreColumns.ID, songId); + values.put(RecentStoreColumns.TIME_PLAYED, System.currentTimeMillis()); + database.insert(RecentStoreColumns.NAME, null, values); + + // if our db is too large, delete the extra items + Cursor oldest = null; + try { + oldest = database.query(RecentStoreColumns.NAME, + new String[]{RecentStoreColumns.TIME_PLAYED}, null, null, null, null, + RecentStoreColumns.TIME_PLAYED + " ASC"); + + if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) { + oldest.moveToPosition(oldest.getCount() - MAX_ITEMS_IN_DB); + long timeOfRecordToKeep = oldest.getLong(0); + + database.delete(RecentStoreColumns.NAME, + RecentStoreColumns.TIME_PLAYED + " < ?", + new String[]{String.valueOf(timeOfRecordToKeep)}); + + } + } finally { + if (oldest != null) { + oldest.close(); + } + } + } finally { + database.setTransactionSuccessful(); + database.endTransaction(); + } + } + + public void removeSongId(final long songId) { + final SQLiteDatabase database = getWritableDatabase(); + database.delete(RecentStoreColumns.NAME, RecentStoreColumns.ID + " = ?", new String[]{ + String.valueOf(songId) + }); + + } + + public void clear() { + final SQLiteDatabase database = getWritableDatabase(); + database.delete(RecentStoreColumns.NAME, null, null); + } + + public boolean contains(long id) { + final SQLiteDatabase database = getReadableDatabase(); + Cursor cursor = database.query(RecentStoreColumns.NAME, + new String[]{RecentStoreColumns.ID}, + RecentStoreColumns.ID + "=?", + new String[]{String.valueOf(id)}, + null, null, null, null); + + boolean containsId = cursor != null && cursor.moveToFirst(); + if (cursor != null) { + cursor.close(); + } + return containsId; + } + + public Cursor queryRecentIds() { + final SQLiteDatabase database = getReadableDatabase(); + return database.query(RecentStoreColumns.NAME, + new String[]{RecentStoreColumns.ID}, null, null, null, null, + RecentStoreColumns.TIME_PLAYED + " DESC"); + } + + public interface RecentStoreColumns { + String NAME = "recent_history"; + + String ID = "song_id"; + + String TIME_PLAYED = "time_played"; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java new file mode 100644 index 00000000..86704afd --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/providers/MusicPlaybackQueueStore.java @@ -0,0 +1,203 @@ +/* +* Copyright (C) 2014 The CyanogenMod Project +* +* 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 code.name.monkey.retromusic.providers; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.provider.BaseColumns; +import android.provider.MediaStore.Audio.AudioColumns; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import io.reactivex.Observable; + +/** + * @author Andrew Neal, modified for Phonograph by Karim Abou Zeid + *

+ * This keeps track of the music playback and history state of the playback service + */ +public class MusicPlaybackQueueStore extends SQLiteOpenHelper { + public static final String DATABASE_NAME = "music_playback_state.db"; + public static final String PLAYING_QUEUE_TABLE_NAME = "playing_queue"; + public static final String ORIGINAL_PLAYING_QUEUE_TABLE_NAME = "original_playing_queue"; + private static final int VERSION = 3; + @Nullable + private static MusicPlaybackQueueStore sInstance = null; + + /** + * Constructor of MusicPlaybackState + * + * @param context The {@link Context} to use + */ + public MusicPlaybackQueueStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + } + + /** + * @param context The {@link Context} to use + * @return A new instance of this class. + */ + @NonNull + public static synchronized MusicPlaybackQueueStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new MusicPlaybackQueueStore(context.getApplicationContext()); + } + return sInstance; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + createTable(db, PLAYING_QUEUE_TABLE_NAME); + createTable(db, ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + } + + private void createTable(@NonNull final SQLiteDatabase db, final String tableName) { + //noinspection StringBufferReplaceableByString + StringBuilder builder = new StringBuilder(); + builder.append("CREATE TABLE IF NOT EXISTS "); + builder.append(tableName); + builder.append("("); + + builder.append(BaseColumns._ID); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.TITLE); + builder.append(" STRING NOT NULL,"); + + builder.append(AudioColumns.TRACK); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.YEAR); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.DURATION); + builder.append(" LONG NOT NULL,"); + + builder.append(AudioColumns.DATA); + builder.append(" STRING NOT NULL,"); + + builder.append(AudioColumns.DATE_MODIFIED); + builder.append(" LONG NOT NULL,"); + + builder.append(AudioColumns.ALBUM_ID); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.ALBUM); + builder.append(" STRING NOT NULL,"); + + builder.append(AudioColumns.ARTIST_ID); + builder.append(" INT NOT NULL,"); + + builder.append(AudioColumns.ARTIST); + builder.append(" STRING NOT NULL);"); + + db.execSQL(builder.toString()); + } + + @Override + public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { + // not necessary yet + db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + onCreate(db); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + // If we ever have downgrade, drop the table to be safe + db.execSQL("DROP TABLE IF EXISTS " + PLAYING_QUEUE_TABLE_NAME); + db.execSQL("DROP TABLE IF EXISTS " + ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + onCreate(db); + } + + public synchronized void saveQueues(@NonNull final ArrayList playingQueue, @NonNull final ArrayList originalPlayingQueue) { + saveQueue(PLAYING_QUEUE_TABLE_NAME, playingQueue); + saveQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME, originalPlayingQueue); + } + + /** + * Clears the existing database and saves the queue into the db so that when the + * app is restarted, the tracks you were listening to is restored + * + * @param queue the queue to save + */ + private synchronized void saveQueue(final String tableName, @NonNull final ArrayList queue) { + final SQLiteDatabase database = getWritableDatabase(); + database.beginTransaction(); + + try { + database.delete(tableName, null, null); + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + } + + final int NUM_PROCESS = 20; + int position = 0; + while (position < queue.size()) { + database.beginTransaction(); + try { + for (int i = position; i < queue.size() && i < position + NUM_PROCESS; i++) { + Song song = queue.get(i); + ContentValues values = new ContentValues(4); + + values.put(BaseColumns._ID, song.id); + values.put(AudioColumns.TITLE, song.title); + values.put(AudioColumns.TRACK, song.trackNumber); + values.put(AudioColumns.YEAR, song.year); + values.put(AudioColumns.DURATION, song.duration); + values.put(AudioColumns.DATA, song.data); + values.put(AudioColumns.DATE_MODIFIED, song.dateModified); + values.put(AudioColumns.ALBUM_ID, song.albumId); + values.put(AudioColumns.ALBUM, song.albumName); + values.put(AudioColumns.ARTIST_ID, song.artistId); + values.put(AudioColumns.ARTIST, song.artistName); + + database.insert(tableName, null, values); + } + database.setTransactionSuccessful(); + } finally { + database.endTransaction(); + position += NUM_PROCESS; + } + } + } + + @NonNull + public Observable> getSavedPlayingQueue() { + return getQueue(PLAYING_QUEUE_TABLE_NAME); + } + + @NonNull + public Observable> getSavedOriginalPlayingQueue() { + return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME); + } + + @NonNull + private Observable> getQueue(@NonNull final String tableName) { + Cursor cursor = getReadableDatabase().query(tableName, null, + null, null, null, null, null); + return SongLoader.getSongs(cursor); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/RepositoryImpl.java b/app/src/main/java/code/name/monkey/retromusic/providers/RepositoryImpl.java new file mode 100644 index 00000000..1baa7247 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/providers/RepositoryImpl.java @@ -0,0 +1,169 @@ +package code.name.monkey.retromusic.providers; + +import android.content.Context; + +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; +import java.io.File; +import java.util.ArrayList; + +import code.name.monkey.retromusic.Injection; +import code.name.monkey.retromusic.loaders.AlbumLoader; +import code.name.monkey.retromusic.loaders.ArtistLoader; +import code.name.monkey.retromusic.loaders.GenreLoader; +import code.name.monkey.retromusic.loaders.HomeLoader; +import code.name.monkey.retromusic.loaders.LastAddedSongsLoader; +import code.name.monkey.retromusic.loaders.PlaylistLoader; +import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; +import code.name.monkey.retromusic.loaders.SearchLoader; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.providers.interfaces.Repository; +import code.name.monkey.retromusic.rest.model.KuGouRawLyric; +import code.name.monkey.retromusic.rest.model.KuGouSearchLyricResult; +import code.name.monkey.retromusic.rest.service.KuGouApiService; +import code.name.monkey.retromusic.util.LyricUtil; +import io.reactivex.Observable; +import io.reactivex.schedulers.Schedulers; + +public class RepositoryImpl implements Repository { + private static RepositoryImpl INSTANCE; + private Context context; + + public RepositoryImpl(Context context) { + this.context = context; + } + + public static synchronized RepositoryImpl getInstance( ) { + if (INSTANCE == null) { + INSTANCE = new RepositoryImpl(RetroApplication.getInstance()); + } + return INSTANCE; + } + + @Override + public Observable> getAllSongs() { + return SongLoader.getAllSongs(context); + } + + @Override + public Observable> getSuggestionSongs() { + return HomeLoader.getRecentAndTopThings(context); + } + + @Override + public Observable getSong(int id) { + return SongLoader.getSong(context, id); + } + + @Override + public Observable> getAllAlbums() { + return AlbumLoader.getAllAlbums(context); + } + + @Override + public Observable> getRecentAlbums() { + return LastAddedSongsLoader.getLastAddedAlbums(context); + } + + @Override + public Observable> getTopAlbums() { + return TopAndRecentlyPlayedTracksLoader.getTopAlbums(context); + } + + @Override + public Observable getAlbum(int albumId) { + return AlbumLoader.getAlbum(context, albumId); + } + + @Override + public Observable> getAllArtists() { + return ArtistLoader.getAllArtists(context); + } + + @Override + public Observable> getRecentArtists() { + return LastAddedSongsLoader.getLastAddedArtists(context); + } + + @Override + public Observable> getTopArtists() { + return TopAndRecentlyPlayedTracksLoader.getTopArtists(context); + } + + @Override + public Observable getArtistById(long artistId) { + return ArtistLoader.getArtist(context, (int) artistId); + } + + @Override + public Observable> getAllPlaylists() { + return PlaylistLoader.getAllPlaylists(context); + } + + @Override + public Observable> getFavoriteSongs() { + return null; + } + + @Override + public Observable> search(String query) { + return SearchLoader.searchAll(context, query); + } + + @Override + public Observable> getPlaylistSongs(Playlist playlist) { + return PlaylistSongsLoader.getPlaylistSongList(context, playlist); + } + + @Override + public Observable> getHomeList() { + return HomeLoader.getHomeLoader(context); + } + + @Override + public Observable> getAllThings() { + return HomeLoader.getRecentAndTopThings(context); + } + + @Override + public Observable> getAllGenres() { + return GenreLoader.getAllGenres(context); + } + + @Override + public Observable> getGenre(int genreId) { + return GenreLoader.getSongs(context, genreId); + } + + @Override + public Observable downloadLrcFile(String title, String artist, long duration) { + KuGouApiService service = Injection.provideKuGouApiService(); + return service.searchLyric(title, String.valueOf(duration)) + .subscribeOn(Schedulers.io()) + .flatMap(kuGouSearchLyricResult -> { + if (kuGouSearchLyricResult.status == 200 + && kuGouSearchLyricResult.candidates != null + && kuGouSearchLyricResult.candidates.size() != 0) { + KuGouSearchLyricResult.Candidates candidates = kuGouSearchLyricResult.candidates.get(0); + return service.getRawLyric(candidates.id, candidates.accesskey); + } else { + return Observable.just(new KuGouRawLyric()); + } + }).map(kuGouRawLyric -> { + if (kuGouRawLyric == null) { + return null; + } + String rawLyric = LyricUtil.decryptBASE64(kuGouRawLyric.content); + if (rawLyric != null && rawLyric.isEmpty()) { + return null; + } + return LyricUtil.writeLrcToLoc(title, artist, rawLyric); + }); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java new file mode 100644 index 00000000..99f22e65 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/providers/SongPlayCountStore.java @@ -0,0 +1,404 @@ +/* +* Copyright (C) 2014 The CyanogenMod Project +* +* 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 code.name.monkey.retromusic.providers; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Interpolator; + +/** + * This database tracks the number of play counts for an individual song. This is used to drive + * the top played tracks as well as the playlist images + */ +public class SongPlayCountStore extends SQLiteOpenHelper { + public static final String DATABASE_NAME = "song_play_count.db"; + private static final int VERSION = 2; + // how many weeks worth of playback to track + private static final int NUM_WEEKS = 52; + @Nullable + private static SongPlayCountStore sInstance = null; + // interpolator curve applied for measuring the curve + @NonNull + private static Interpolator sInterpolator = new AccelerateInterpolator(1.5f); + // how high to multiply the interpolation curve + @SuppressWarnings("FieldCanBeLocal") + private static int INTERPOLATOR_HEIGHT = 50; + + // how high the base value is. The ratio of the Height to Base is what really matters + @SuppressWarnings("FieldCanBeLocal") + private static int INTERPOLATOR_BASE = 25; + + @SuppressWarnings("FieldCanBeLocal") + private static int ONE_WEEK_IN_MS = 1000 * 60 * 60 * 24 * 7; + + @NonNull + private static String WHERE_ID_EQUALS = SongPlayCountColumns.ID + "=?"; + + // number of weeks since epoch time + private int mNumberOfWeeksSinceEpoch; + + // used to track if we've walked through the db and updated all the rows + private boolean mDatabaseUpdated; + + public SongPlayCountStore(final Context context) { + super(context, DATABASE_NAME, null, VERSION); + + long msSinceEpoch = System.currentTimeMillis(); + mNumberOfWeeksSinceEpoch = (int) (msSinceEpoch / ONE_WEEK_IN_MS); + + mDatabaseUpdated = false; + } + + /** + * @param context The {@link Context} to use + * @return A new instance of this class. + */ + @NonNull + public static synchronized SongPlayCountStore getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new SongPlayCountStore(context.getApplicationContext()); + } + return sInstance; + } + + /** + * Calculates the score of the song given the play counts + * + * @param playCounts an array of the # of times a song has been played for each week + * where playCounts[N] is the # of times it was played N weeks ago + * @return the score + */ + private static float calculateScore(@Nullable final int[] playCounts) { + if (playCounts == null) { + return 0; + } + + float score = 0; + for (int i = 0; i < Math.min(playCounts.length, NUM_WEEKS); i++) { + score += playCounts[i] * getScoreMultiplierForWeek(i); + } + + return score; + } + + /** + * Gets the column name for each week # + * + * @param week number + * @return the column name + */ + @NonNull + private static String getColumnNameForWeek(final int week) { + return SongPlayCountColumns.WEEK_PLAY_COUNT + String.valueOf(week); + } + + /** + * Gets the score multiplier for each week + * + * @param week number + * @return the multiplier to apply + */ + private static float getScoreMultiplierForWeek(final int week) { + return sInterpolator.getInterpolation(1 - (week / (float) NUM_WEEKS)) * INTERPOLATOR_HEIGHT + + INTERPOLATOR_BASE; + } + + /** + * For some performance gain, return a static value for the column index for a week + * WARNING: This function assumes you have selected all columns for it to work + * + * @param week number + * @return column index of that week + */ + private static int getColumnIndexForWeek(final int week) { + // ID, followed by the weeks columns + return 1 + week; + } + + @Override + public void onCreate(@NonNull final SQLiteDatabase db) { + // create the play count table + // WARNING: If you change the order of these columns + // please update getColumnIndexForWeek + StringBuilder builder = new StringBuilder(); + builder.append("CREATE TABLE IF NOT EXISTS "); + builder.append(SongPlayCountColumns.NAME); + builder.append("("); + builder.append(SongPlayCountColumns.ID); + builder.append(" INT UNIQUE,"); + + for (int i = 0; i < NUM_WEEKS; i++) { + builder.append(getColumnNameForWeek(i)); + builder.append(" INT DEFAULT 0,"); + } + + builder.append(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX); + builder.append(" INT NOT NULL,"); + + builder.append(SongPlayCountColumns.PLAY_COUNT_SCORE); + builder.append(" REAL DEFAULT 0);"); + + db.execSQL(builder.toString()); + } + + @Override + public void onUpgrade(@NonNull final SQLiteDatabase db, final int oldVersion, final int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME); + onCreate(db); + } + + @Override + public void onDowngrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + // If we ever have downgrade, drop the table to be safe + db.execSQL("DROP TABLE IF EXISTS " + SongPlayCountColumns.NAME); + onCreate(db); + } + + /** + * Increases the play count of a song by 1 + * + * @param songId The song id to increase the play count + */ + public void bumpPlayCount(final long songId) { + if (songId == -1) { + return; + } + + final SQLiteDatabase database = getWritableDatabase(); + updateExistingRow(database, songId, true); + } + + /** + * This creates a new entry that indicates a song has been played once as well as its score + * + * @param database a write able database + * @param songId the id of the track + */ + private void createNewPlayedEntry(@NonNull final SQLiteDatabase database, final long songId) { + // no row exists, create a new one + float newScore = getScoreMultiplierForWeek(0); + int newPlayCount = 1; + + final ContentValues values = new ContentValues(3); + values.put(SongPlayCountColumns.ID, songId); + values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, newScore); + values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch); + values.put(getColumnNameForWeek(0), newPlayCount); + + database.insert(SongPlayCountColumns.NAME, null, values); + } + + /** + * This function will take a song entry and update it to the latest week and increase the count + * for the current week by 1 if necessary + * + * @param database a writeable database + * @param id the id of the track to bump + * @param bumpCount whether to bump the current's week play count by 1 and adjust the score + */ + private void updateExistingRow(@NonNull final SQLiteDatabase database, final long id, boolean bumpCount) { + String stringId = String.valueOf(id); + + // begin the transaction + database.beginTransaction(); + + // get the cursor of this content inside the transaction + final Cursor cursor = database.query(SongPlayCountColumns.NAME, null, WHERE_ID_EQUALS, + new String[]{stringId}, null, null, null); + + // if we have a result + if (cursor != null && cursor.moveToFirst()) { + // figure how many weeks since we last updated + int lastUpdatedIndex = cursor.getColumnIndex(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX); + int lastUpdatedWeek = cursor.getInt(lastUpdatedIndex); + int weekDiff = mNumberOfWeeksSinceEpoch - lastUpdatedWeek; + + // if it's more than the number of weeks we track, delete it and create a new entry + if (Math.abs(weekDiff) >= NUM_WEEKS) { + // this entry needs to be dropped since it is too outdated + deleteEntry(database, stringId); + if (bumpCount) { + createNewPlayedEntry(database, id); + } + } else if (weekDiff != 0) { + // else, shift the weeks + int[] playCounts = new int[NUM_WEEKS]; + + if (weekDiff > 0) { + // time is shifted forwards + for (int i = 0; i < NUM_WEEKS - weekDiff; i++) { + playCounts[i + weekDiff] = cursor.getInt(getColumnIndexForWeek(i)); + } + } else if (weekDiff < 0) { + // time is shifted backwards (by user) - nor typical behavior but we + // will still handle it + + // since weekDiff is -ve, NUM_WEEKS + weekDiff is the real # of weeks we have to + // transfer. Then we transfer the old week i - weekDiff to week i + // for example if the user shifted back 2 weeks, ie -2, then for 0 to + // NUM_WEEKS + (-2) we set the new week i = old week i - (-2) or i+2 + for (int i = 0; i < NUM_WEEKS + weekDiff; i++) { + playCounts[i] = cursor.getInt(getColumnIndexForWeek(i - weekDiff)); + } + } + + // bump the count + if (bumpCount) { + playCounts[0]++; + } + + float score = calculateScore(playCounts); + + // if the score is non-existant, then delete it + if (score < .01f) { + deleteEntry(database, stringId); + } else { + // create the content values + ContentValues values = new ContentValues(NUM_WEEKS + 2); + values.put(SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX, mNumberOfWeeksSinceEpoch); + values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score); + + for (int i = 0; i < NUM_WEEKS; i++) { + values.put(getColumnNameForWeek(i), playCounts[i]); + } + + // update the entry + database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS, + new String[]{stringId}); + } + } else if (bumpCount) { + // else no shifting, just update the scores + ContentValues values = new ContentValues(2); + + // increase the score by a single score amount + int scoreIndex = cursor.getColumnIndex(SongPlayCountColumns.PLAY_COUNT_SCORE); + float score = cursor.getFloat(scoreIndex) + getScoreMultiplierForWeek(0); + values.put(SongPlayCountColumns.PLAY_COUNT_SCORE, score); + + // increase the play count by 1 + values.put(getColumnNameForWeek(0), cursor.getInt(getColumnIndexForWeek(0)) + 1); + + // update the entry + database.update(SongPlayCountColumns.NAME, values, WHERE_ID_EQUALS, + new String[]{stringId}); + } + + cursor.close(); + } else if (bumpCount) { + // if we have no existing results, create a new one + createNewPlayedEntry(database, id); + } + + database.setTransactionSuccessful(); + database.endTransaction(); + } + + public void clear() { + final SQLiteDatabase database = getWritableDatabase(); + database.delete(SongPlayCountColumns.NAME, null, null); + } + + /** + * Gets a cursor containing the top songs played. Note this only returns songs that have been + * played at least once in the past NUM_WEEKS + * + * @param numResults number of results to limit by. If <= 0 it returns all results + * @return the top tracks + */ + public Cursor getTopPlayedResults(int numResults) { + updateResults(); + + final SQLiteDatabase database = getReadableDatabase(); + return database.query(SongPlayCountColumns.NAME, new String[]{SongPlayCountColumns.ID}, + null, null, null, null, SongPlayCountColumns.PLAY_COUNT_SCORE + " DESC", + (numResults <= 0 ? null : String.valueOf(numResults))); + } + + /** + * This updates all the results for the getTopPlayedResults so that we can get an + * accurate list of the top played results + */ + private synchronized void updateResults() { + if (mDatabaseUpdated) { + return; + } + + final SQLiteDatabase database = getWritableDatabase(); + + database.beginTransaction(); + + int oldestWeekWeCareAbout = mNumberOfWeeksSinceEpoch - NUM_WEEKS + 1; + // delete rows we don't care about anymore + database.delete(SongPlayCountColumns.NAME, SongPlayCountColumns.LAST_UPDATED_WEEK_INDEX + + " < " + oldestWeekWeCareAbout, null); + + // get the remaining rows + Cursor cursor = database.query(SongPlayCountColumns.NAME, + new String[]{SongPlayCountColumns.ID}, + null, null, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + // for each row, update it + do { + updateExistingRow(database, cursor.getLong(0), false); + } while (cursor.moveToNext()); + + cursor.close(); + } + + mDatabaseUpdated = true; + database.setTransactionSuccessful(); + database.endTransaction(); + } + + /** + * @param songId The song Id to remove. + */ + public void removeItem(final long songId) { + final SQLiteDatabase database = getWritableDatabase(); + deleteEntry(database, String.valueOf(songId)); + } + + /** + * Deletes the entry + * + * @param database database to use + * @param stringId id to delete + */ + private void deleteEntry(@NonNull final SQLiteDatabase database, final String stringId) { + database.delete(SongPlayCountColumns.NAME, WHERE_ID_EQUALS, new String[]{stringId}); + } + + public interface SongPlayCountColumns { + + String NAME = "song_play_count"; + + String ID = "song_id"; + + String WEEK_PLAY_COUNT = "week"; + + String LAST_UPDATED_WEEK_INDEX = "week_index"; + + String PLAY_COUNT_SCORE = "play_count_score"; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/providers/interfaces/Repository.java b/app/src/main/java/code/name/monkey/retromusic/providers/interfaces/Repository.java new file mode 100644 index 00000000..11d3d7d4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/providers/interfaces/Repository.java @@ -0,0 +1,60 @@ +package code.name.monkey.retromusic.providers.interfaces; + +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; +import io.reactivex.Observable; +import java.io.File; +import java.util.ArrayList; + +/** + * Created by hemanths on 11/08/17. + */ + +public interface Repository { + + Observable> getAllSongs(); + + Observable> getSuggestionSongs(); + + Observable getSong(int id); + + Observable> getAllAlbums(); + + Observable> getRecentAlbums(); + + Observable> getTopAlbums(); + + Observable getAlbum(int albumId); + + Observable> getAllArtists(); + + Observable> getRecentArtists(); + + Observable> getTopArtists(); + + + Observable getArtistById(long artistId); + + Observable> getAllPlaylists(); + + Observable> getFavoriteSongs(); + + Observable> search(String query); + + Observable> getPlaylistSongs(Playlist playlist); + + Observable> getHomeList(); + + Observable> getAllThings(); + + Observable> getAllGenres(); + + Observable> getGenre(int genreId); + + Observable downloadLrcFile(final String title, final String artist, final long duration); + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/KogouClient.java b/app/src/main/java/code/name/monkey/retromusic/rest/KogouClient.java new file mode 100644 index 00000000..7ba3fb29 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/KogouClient.java @@ -0,0 +1,76 @@ +package code.name.monkey.retromusic.rest; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.rest.service.KuGouApiService; +import java.io.File; +import okhttp3.Cache; +import okhttp3.Call; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + +/** + * Created by hemanths on 23/08/17. + */ + +public class KogouClient { + + private static final String BASE_URL = Constants.BASE_API_URL_KUGOU; + + private KuGouApiService apiService; + + public KogouClient() { + this(createDefaultOkHttpClientBuilder().build()); + } + + public KogouClient(@NonNull Call.Factory client) { + Retrofit restAdapter = new Retrofit.Builder() + .baseUrl(BASE_URL) + .callFactory(client) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + + apiService = restAdapter.create(KuGouApiService.class); + } + + @Nullable + public static Cache createDefaultCache(Context context) { + File cacheDir = new File(context.getCacheDir().getAbsolutePath(), "/okhttp-lastfm/"); + if (cacheDir.mkdirs() || cacheDir.isDirectory()) { + return new Cache(cacheDir, 1024 * 1024 * 10); + } + return null; + } + + public static Interceptor createCacheControlInterceptor() { + return chain -> { + Request modifiedRequest = chain.request().newBuilder() + .addHeader("Cache-Control", String.format("max-age=%d, max-stale=%d", 31536000, 31536000)) + .build(); + return chain.proceed(modifiedRequest); + }; + } + + public static OkHttpClient.Builder createDefaultOkHttpClientBuilder() { + HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); + interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); + + return new OkHttpClient.Builder() + .addInterceptor(interceptor) + .cache(createDefaultCache(RetroApplication.getInstance())) + .addInterceptor(createCacheControlInterceptor()); + } + + public KuGouApiService getApiService() { + return apiService; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/LastFMRestClient.java b/app/src/main/java/code/name/monkey/retromusic/rest/LastFMRestClient.java new file mode 100644 index 00000000..aae47690 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/LastFMRestClient.java @@ -0,0 +1,66 @@ +package code.name.monkey.retromusic.rest; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import code.name.monkey.retromusic.rest.service.LastFMService; +import java.io.File; +import okhttp3.Cache; +import okhttp3.Call; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; + + +public class LastFMRestClient { + + public static final String BASE_URL = "http://ws.audioscrobbler.com/2.0/"; + + private LastFMService apiService; + + public LastFMRestClient(@NonNull Context context) { + this(createDefaultOkHttpClientBuilder(context).build()); + } + + public LastFMRestClient(@NonNull Call.Factory client) { + Retrofit restAdapter = new Retrofit.Builder() + .baseUrl(BASE_URL) + .callFactory(client) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); + + apiService = restAdapter.create(LastFMService.class); + } + + @Nullable + public static Cache createDefaultCache(Context context) { + File cacheDir = new File(context.getCacheDir().getAbsolutePath(), "/okhttp-lastfm/"); + if (cacheDir.mkdirs() || cacheDir.isDirectory()) { + return new Cache(cacheDir, 1024 * 1024 * 10); + } + return null; + } + + public static Interceptor createCacheControlInterceptor() { + return chain -> { + Request modifiedRequest = chain.request().newBuilder() + .addHeader("Cache-Control", String.format("max-age=%d, max-stale=%d", 31536000, 31536000)) + .build(); + return chain.proceed(modifiedRequest); + }; + } + + public static OkHttpClient.Builder createDefaultOkHttpClientBuilder(Context context) { + return new OkHttpClient.Builder() + .cache(createDefaultCache(context)) + .addInterceptor(createCacheControlInterceptor()); + } + + public LastFMService getApiService() { + return apiService; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/model/KuGouRawLyric.java b/app/src/main/java/code/name/monkey/retromusic/rest/model/KuGouRawLyric.java new file mode 100644 index 00000000..1ce96f46 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/model/KuGouRawLyric.java @@ -0,0 +1,41 @@ +package code.name.monkey.retromusic.rest.model; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by hefuyi on 2017/1/20. + */ + +public class KuGouRawLyric { + + private static final String CHARSET = "charset"; + private static final String CONTENT = "content"; + private static final String FMT = "fmt"; + private static final String INFO = "info"; + private static final String STATUS = "status"; + + @SerializedName(CHARSET) + public String charset; + + @SerializedName(CONTENT) + public String content; + + @SerializedName(FMT) + public String fmt; + @SerializedName(INFO) + public String info; + @SerializedName(STATUS) + public int status; + + @Override + public String toString() { + return "KuGouRawLyric{" + + "charset='" + charset + '\'' + + ", content='" + content + '\'' + + ", fmt='" + fmt + '\'' + + ", info='" + info + '\'' + + ", status=" + status + + '}'; + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/model/KuGouSearchLyricResult.java b/app/src/main/java/code/name/monkey/retromusic/rest/model/KuGouSearchLyricResult.java new file mode 100644 index 00000000..43f25be1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/model/KuGouSearchLyricResult.java @@ -0,0 +1,90 @@ +package code.name.monkey.retromusic.rest.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Created by hefuyi on 2017/1/20. + */ + +public class KuGouSearchLyricResult { + + private static final String INFO = "info"; + private static final String STATUS = "status"; + private static final String PROPOSAL = "proposal"; + private static final String KEYWORD = "keyword"; + private static final String CANDIDATES = "candidates"; + + @SerializedName(INFO) + public String info; + + @SerializedName(STATUS) + public int status; + + @SerializedName(PROPOSAL) + public String proposal; + + @SerializedName(KEYWORD) + public String keyword; + + @SerializedName(CANDIDATES) + public List candidates; + + @Override + public String toString() { + return "KuGouSearchLyricResult{" + + "info='" + info + '\'' + + ", status=" + status + + ", proposal='" + proposal + '\'' + + ", keyword='" + keyword + '\'' + + ", candidates=" + candidates + + '}'; + } + + public static class Candidates { + private static final String NICKNAME = "nickname"; + private static final String ACCESSKEY = "accesskey"; + private static final String SCORE = "score"; + private static final String DURATION = "duration"; + private static final String UID = "uid"; + private static final String SONG = "song"; + private static final String ID = "id"; + private static final String SINGER = "singer"; + private static final String LANGUAGE = "language"; + @SerializedName(NICKNAME) + public String nickname; + @SerializedName(ACCESSKEY) + public String accesskey; + @SerializedName(SCORE) + public int score; + @SerializedName(DURATION) + public long duration; + @SerializedName(UID) + public String uid; + @SerializedName(SONG) + public String songName; + @SerializedName(ID) + public String id; + @SerializedName(SINGER) + public String singer; + @SerializedName(LANGUAGE) + public String language; + + @Override + public String toString() { + return "Candidates{" + + "nickname='" + nickname + '\'' + + ", accesskey='" + accesskey + '\'' + + ", score=" + score + + ", duration=" + duration + + ", uid='" + uid + '\'' + + ", songName='" + songName + '\'' + + ", id='" + id + '\'' + + ", singer='" + singer + '\'' + + ", language='" + language + '\'' + + '}'; + } + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmAlbum.java b/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmAlbum.java new file mode 100644 index 00000000..408fb224 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmAlbum.java @@ -0,0 +1,111 @@ +package code.name.monkey.retromusic.rest.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class LastFmAlbum { + @Expose + private Album album; + + public Album getAlbum() { + return album; + } + + public void setAlbum(Album album) { + this.album = album; + } + + public static class Album { + @Expose + private Tags tags; + @Expose + private List image = new ArrayList<>(); + @Expose + private Wiki wiki; + + public List getImage() { + return image; + } + + public void setImage(List image) { + this.image = image; + } + + public Wiki getWiki() { + return wiki; + } + + public void setWiki(Wiki wiki) { + this.wiki = wiki; + } + + public Tags getTags() { + return tags; + } + + public static class Image { + @SerializedName("#text") + @Expose + private String Text; + @Expose + private String size; + + public String getText() { + return Text; + } + + public void setText(String Text) { + this.Text = Text; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + } + + public class Tags { + @Expose + private List tag = null; + + public List getTag() { + return tag; + } + } + + public class Tag { + @Expose + private String name; + + @Expose + private String url; + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + } + + public class Wiki { + @Expose + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmArtist.java b/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmArtist.java new file mode 100644 index 00000000..03335a57 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmArtist.java @@ -0,0 +1,80 @@ +package code.name.monkey.retromusic.rest.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class LastFmArtist { + @Expose + private Artist artist; + + public Artist getArtist() { + return artist; + } + + public void setArtist(Artist artist) { + this.artist = artist; + } + + public static class Artist { + @Expose + private List image = new ArrayList<>(); + @Expose + private Bio bio; + + public List getImage() { + return image; + } + + public void setImage(List image) { + this.image = image; + } + + public Bio getBio() { + return bio; + } + + public void setBio(Bio bio) { + this.bio = bio; + } + + public class Bio { + @Expose + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } + + public static class Image { + @SerializedName("#text") + @Expose + private String Text; + @Expose + private String size; + + public String getText() { + return Text; + } + + public void setText(String Text) { + this.Text = Text; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmTrack.java b/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmTrack.java new file mode 100644 index 00000000..f8623833 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/model/LastFmTrack.java @@ -0,0 +1,174 @@ +package code.name.monkey.retromusic.rest.model; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * Created by hemanths on 15/06/17. + */ + +public class LastFmTrack { + + @Expose + private Track track; + + public Track getTrack() { + return track; + } + + public void setTrack(Track track) { + this.track = track; + } + + public static class Track { + @SerializedName("name") + @Expose + private String name; + @Expose + private Album album; + @Expose + private Wiki wiki; + @Expose + private Toptags toptags; + @Expose + private Artist artist; + + public Album getAlbum() { + return album; + } + + public Wiki getWiki() { + return wiki; + } + + public String getName() { + return name; + } + + public Toptags getToptags() { + return toptags; + } + + public static class Artist { + + @Expose + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class Wiki { + @Expose + private String published; + + public String getPublished() { + return published; + } + + public void setPublished(String published) { + this.published = published; + } + } + + public static class Toptags { + @Expose + private List tag = null; + + + public List getTag() { + return tag; + } + + public static class Tag { + @Expose + private String name; + + public String getName() { + return name; + } + } + } + + public static class Album { + @Expose + private String artist; + @Expose + private List image = null; + @Expose + private String title; + @SerializedName("@attr") + @Expose + private Attr attr; + + public Attr getAttr() { + return attr; + } + + public void setAttr(Attr attr) { + this.attr = attr; + } + + public String getArtist() { + return artist; + } + + public void setArtist(String artist) { + this.artist = artist; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List getImage() { + return image; + } + + public void setImage(List image) { + this.image = image; + } + + public static class Attr { + @Expose + private String position; + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + } + + public class Image { + + @SerializedName("#text") + @Expose + private String text; + @Expose + private String size; + + public String getSize() { + return size; + } + + public String getText() { + return text; + } + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/service/KuGouApiService.java b/app/src/main/java/code/name/monkey/retromusic/rest/service/KuGouApiService.java new file mode 100644 index 00000000..ea11bd96 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/service/KuGouApiService.java @@ -0,0 +1,21 @@ +package code.name.monkey.retromusic.rest.service; + +import code.name.monkey.retromusic.rest.model.KuGouRawLyric; +import code.name.monkey.retromusic.rest.model.KuGouSearchLyricResult; + +import io.reactivex.Observable; +import retrofit2.http.GET; +import retrofit2.http.Query; + +/** + * Created by hemanths on 28/07/17. + */ + +public interface KuGouApiService { + + @GET("search?ver=1&man=yes&client=pc") + Observable searchLyric(@Query("keyword") String songName, @Query("duration") String duration); + + @GET("download?ver=1&client=pc&fmt=lrc&charset=utf8") + Observable getRawLyric(@Query("id") String id, @Query("accesskey") String accesskey); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/rest/service/LastFMService.java b/app/src/main/java/code/name/monkey/retromusic/rest/service/LastFMService.java new file mode 100644 index 00000000..4a0ab117 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/rest/service/LastFMService.java @@ -0,0 +1,33 @@ +package code.name.monkey.retromusic.rest.service; + +import android.support.annotation.Nullable; + +import code.name.monkey.retromusic.BuildConfig; +import code.name.monkey.retromusic.rest.model.LastFmAlbum; +import code.name.monkey.retromusic.rest.model.LastFmArtist; +import code.name.monkey.retromusic.rest.model.LastFmTrack; + +import io.reactivex.Observable; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.Query; + + + +public interface LastFMService { + String API_KEY = "bd9c6ea4d55ec9ed3af7d276e5ece304"; + //String API_KEY = BuildConfig.LASTFM_API_KEY; + //String BASE_QUERY_PARAMETERS = "?format=json&autocorrect=1&api_key=" + API_KEY; + String BASE_QUERY_PARAMETERS = "?format=json&autocorrect=1&api_key=" + API_KEY; + String METHOD_TRACK = "track.getInfo"; + + @GET(BASE_QUERY_PARAMETERS + "&method=album.getinfo") + Observable getAlbumInfo(@Query("album") String albumName, @Query("artist") String artistName, @Nullable @Query("lang") String language); + + @GET("?api_key=" + BuildConfig.LASTFM_API_KEY + "&format=json&autocorrect=1" + "&method=" + METHOD_TRACK) + Observable getTrackInfo(@Query("artist") String artist, @Query("track") String track); + + @GET(BASE_QUERY_PARAMETERS + "&method=artist.getinfo") + Call getArtistInfo(@Query("artist") String artistName, @Nullable @Query("lang") String language, @Nullable @Header("Cache-Control") String cacheControl); +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MediaButtonIntentReceiver.java b/app/src/main/java/code/name/monkey/retromusic/service/MediaButtonIntentReceiver.java new file mode 100644 index 00000000..a728e7b4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/MediaButtonIntentReceiver.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2007 The Android Open Source Project 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. + */ + +// Modified for Phonograph by Karim Abou Zeid (kabouzeid). + +package code.name.monkey.retromusic.service; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Log; +import android.view.KeyEvent; + +import code.name.monkey.retromusic.BuildConfig; + +import static code.name.monkey.retromusic.Constants.ACTION_PAUSE; +import static code.name.monkey.retromusic.Constants.ACTION_PLAY; +import static code.name.monkey.retromusic.Constants.ACTION_REWIND; +import static code.name.monkey.retromusic.Constants.ACTION_SKIP; +import static code.name.monkey.retromusic.Constants.ACTION_STOP; +import static code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE; + +/** + * Used to control headset playback. + * Single press: pause/resume + * Double press: next track + * Triple press: previous track + */ +public class MediaButtonIntentReceiver extends BroadcastReceiver { + public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName(); + private static final boolean DEBUG = BuildConfig.DEBUG; + private static final int MSG_HEADSET_DOUBLE_CLICK_TIMEOUT = 2; + + private static final int DOUBLE_CLICK = 400; + + private static WakeLock mWakeLock = null; + private static int mClickCounter = 0; + private static long mLastClickTime = 0; + + @SuppressLint("HandlerLeak") // false alarm, handler is already static + private static Handler mHandler = new Handler() { + + @Override + public void handleMessage(final Message msg) { + switch (msg.what) { + case MSG_HEADSET_DOUBLE_CLICK_TIMEOUT: + final int clickCount = msg.arg1; + final String command; + + if (DEBUG) Log.v(TAG, "Handling headset click, count = " + clickCount); + switch (clickCount) { + case 1: + command = ACTION_TOGGLE_PAUSE; + break; + case 2: + command = ACTION_SKIP; + break; + case 3: + command = ACTION_REWIND; + break; + default: + command = null; + break; + } + + if (command != null) { + final Context context = (Context) msg.obj; + startService(context, command); + } + break; + } + releaseWakeLockIfHandlerIdle(); + } + }; + + public static boolean handleIntent(final Context context, final Intent intent) { + final String intentAction = intent.getAction(); + if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) { + final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + if (event == null) { + return false; + } + + final int keycode = event.getKeyCode(); + final int action = event.getAction(); + final long eventTime = event.getEventTime() != 0 ? + event.getEventTime() : System.currentTimeMillis(); + + String command = null; + switch (keycode) { + case KeyEvent.KEYCODE_MEDIA_STOP: + command = ACTION_STOP; + break; + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + command = ACTION_TOGGLE_PAUSE; + break; + case KeyEvent.KEYCODE_MEDIA_NEXT: + command = ACTION_SKIP; + break; + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + command = ACTION_REWIND; + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + command = ACTION_PAUSE; + break; + case KeyEvent.KEYCODE_MEDIA_PLAY: + command = ACTION_PLAY; + break; + } + if (command != null) { + if (action == KeyEvent.ACTION_DOWN) { + if (event.getRepeatCount() == 0) { + // Only consider the first event in a sequence, not the repeat events, + // so that we don't trigger in cases where the first event went to + // a different app (e.g. when the user ends a phone call by + // long pressing the headset button) + + // The service may or may not be running, but we need to send it + // a command. + if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { + if (eventTime - mLastClickTime >= DOUBLE_CLICK) { + mClickCounter = 0; + } + + mClickCounter++; + if (DEBUG) Log.v(TAG, "Got headset click, count = " + mClickCounter); + mHandler.removeMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT); + + Message msg = mHandler.obtainMessage( + MSG_HEADSET_DOUBLE_CLICK_TIMEOUT, mClickCounter, 0, context); + + long delay = mClickCounter < 3 ? DOUBLE_CLICK : 0; + if (mClickCounter >= 3) { + mClickCounter = 0; + } + mLastClickTime = eventTime; + acquireWakeLockAndSendMessage(context, msg, delay); + } else { + startService(context, command); + } + return true; + } + } + } + } + return false; + } + + private static void startService(Context context, String command) { + final Intent intent = new Intent(context, MusicService.class); + intent.setAction(command); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); + } else { + context.startService(intent); + } + } + + private static void acquireWakeLockAndSendMessage(Context context, Message msg, long delay) { + if (mWakeLock == null) { + Context appContext = context.getApplicationContext(); + PowerManager pm = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE); + if (pm != null) { + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "RetroMusicApp:Wakelock headset button"); + } + mWakeLock.setReferenceCounted(false); + } + if (DEBUG) Log.v(TAG, "Acquiring wake lock and sending " + msg.what); + // Make sure we don't indefinitely hold the wake lock under any circumstances + mWakeLock.acquire(10000); + + mHandler.sendMessageDelayed(msg, delay); + } + + private static void releaseWakeLockIfHandlerIdle() { + if (mHandler.hasMessages(MSG_HEADSET_DOUBLE_CLICK_TIMEOUT)) { + if (DEBUG) Log.v(TAG, "Handler still has messages pending, not releasing wake lock"); + return; + } + + if (mWakeLock != null) { + if (DEBUG) Log.v(TAG, "Releasing wake lock"); + mWakeLock.release(); + mWakeLock = null; + } + } + + @Override + public void onReceive(final Context context, final Intent intent) { + if (DEBUG) Log.v(TAG, "Received intent: " + intent); + if (handleIntent(context, intent) && isOrderedBroadcast()) { + abortBroadcast(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java new file mode 100644 index 00000000..d7a7e23b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/MultiPlayer.java @@ -0,0 +1,334 @@ +package code.name.monkey.retromusic.service; + +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.audiofx.AudioEffect; +import android.net.Uri; +import android.os.PowerManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.widget.Toast; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.service.playback.Playback; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * @author Andrew Neal, Karim Abou Zeid (kabouzeid) + */ +public class MultiPlayer implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { + public static final String TAG = MultiPlayer.class.getSimpleName(); + + private MediaPlayer mCurrentMediaPlayer = new MediaPlayer(); + private MediaPlayer mNextMediaPlayer; + + private Context context; + @Nullable + private Playback.PlaybackCallbacks callbacks; + + private boolean mIsInitialized = false; + + /** + * Constructor of MultiPlayer + */ + MultiPlayer(final Context context) { + this.context = context; + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + } + + /** + * @param path The path of the file, or the http/rtsp URL of the stream + * you want to play + * @return True if the player has been prepared and is + * ready to play, false otherwise + */ + @Override + public boolean setDataSource(@NonNull final String path) { + mIsInitialized = false; + mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path); + if (mIsInitialized) { + setNextDataSource(null); + } + return mIsInitialized; + } + + /** + * @param player The {@link MediaPlayer} to use + * @param path The path of the file, or the http/rtsp URL of the stream + * you want to play + * @return True if the player has been prepared and is + * ready to play, false otherwise + */ + private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) { + if (context == null) { + return false; + } + try { + player.reset(); + player.setOnPreparedListener(null); + if (path.startsWith("content://")) { + player.setDataSource(context, Uri.parse(path)); + } else { + player.setDataSource(path); + } + player.setAudioStreamType(AudioManager.STREAM_MUSIC); + player.prepare(); + } catch (Exception e) { + return false; + } + player.setOnCompletionListener(this); + player.setOnErrorListener(this); + final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId()); + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); + intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); + context.sendBroadcast(intent); + return true; + } + + /** + * Set the MediaPlayer to start when this MediaPlayer finishes playback. + * + * @param path The path of the file, or the http/rtsp URL of the stream + * you want to play + */ + @Override + public void setNextDataSource(@Nullable final String path) { + if (context == null) { + return; + } + try { + mCurrentMediaPlayer.setNextMediaPlayer(null); + } catch (IllegalArgumentException e) { + Log.i(TAG, "Next media player is current one, continuing"); + } catch (IllegalStateException e) { + Log.e(TAG, "Media player not initialized!"); + return; + } + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + mNextMediaPlayer = null; + } + if (path == null) { + return; + } + if (PreferenceUtil.getInstance(context).gaplessPlayback()) { + mNextMediaPlayer = new MediaPlayer(); + mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + mNextMediaPlayer.setAudioSessionId(getAudioSessionId()); + if (setDataSourceImpl(mNextMediaPlayer, path)) { + try { + mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer); + } catch (@NonNull IllegalArgumentException | IllegalStateException e) { + Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e); + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + mNextMediaPlayer = null; + } + } + } else { + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + mNextMediaPlayer = null; + } + } + } + } + + /** + * Sets the callbacks + * + * @param callbacks The callbacks to use + */ + @Override + public void setCallbacks(@Nullable final Playback.PlaybackCallbacks callbacks) { + this.callbacks = callbacks; + } + + /** + * @return True if the player is ready to go, false otherwise + */ + @Override + public boolean isInitialized() { + return mIsInitialized; + } + + /** + * Starts or resumes playback. + */ + @Override + public boolean start() { + try { + mCurrentMediaPlayer.start(); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** + * Resets the MediaPlayer to its uninitialized state. + */ + @Override + public void stop() { + mCurrentMediaPlayer.reset(); + mIsInitialized = false; + } + + /** + * Releases resources associated with this MediaPlayer object. + */ + @Override + public void release() { + stop(); + mCurrentMediaPlayer.release(); + if (mNextMediaPlayer != null) { + mNextMediaPlayer.release(); + } + } + + /** + * Pauses playback. Call start() to resume. + */ + @Override + public boolean pause() { + try { + mCurrentMediaPlayer.pause(); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** + * Checks whether the MultiPlayer is playing. + */ + @Override + public boolean isPlaying() { + return mIsInitialized && mCurrentMediaPlayer.isPlaying(); + } + + /** + * Gets the duration of the file. + * + * @return The duration in milliseconds + */ + @Override + public int duration() { + if (!mIsInitialized) { + return -1; + } + try { + return mCurrentMediaPlayer.getDuration(); + } catch (IllegalStateException e) { + return -1; + } + } + + /** + * Gets the current playback position. + * + * @return The current position in milliseconds + */ + @Override + public int position() { + if (!mIsInitialized) { + return -1; + } + try { + return mCurrentMediaPlayer.getCurrentPosition(); + } catch (IllegalStateException e) { + return -1; + } + } + + /** + * Gets the current playback position. + * + * @param whereto The offset in milliseconds from the start to seek to + * @return The offset in milliseconds from the start to seek to + */ + @Override + public int seek(final int whereto) { + try { + mCurrentMediaPlayer.seekTo(whereto); + return whereto; + } catch (IllegalStateException e) { + return -1; + } + } + + @Override + public boolean setVolume(final float vol) { + try { + mCurrentMediaPlayer.setVolume(vol, vol); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + /** + * Sets the audio session ID. + * + * @param sessionId The audio session ID + */ + @Override + public boolean setAudioSessionId(final int sessionId) { + try { + mCurrentMediaPlayer.setAudioSessionId(sessionId); + return true; + } catch (@NonNull IllegalArgumentException | IllegalStateException e) { + return false; + } + } + + /** + * Returns the audio session ID. + * + * @return The current audio session ID. + */ + @Override + public int getAudioSessionId() { + return mCurrentMediaPlayer.getAudioSessionId(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onError(final MediaPlayer mp, final int what, final int extra) { + mIsInitialized = false; + mCurrentMediaPlayer.release(); + mCurrentMediaPlayer = new MediaPlayer(); + mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); + if (context != null) { + Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void onCompletion(final MediaPlayer mp) { + if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) { + mIsInitialized = false; + mCurrentMediaPlayer.release(); + mCurrentMediaPlayer = mNextMediaPlayer; + mIsInitialized = true; + mNextMediaPlayer = null; + if (callbacks != null) + callbacks.onTrackWentToNext(); + } else { + if (callbacks != null) + callbacks.onTrackEnded(); + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java new file mode 100644 index 00000000..0007eb23 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -0,0 +1,1447 @@ +package code.name.monkey.retromusic.service; + +import android.app.PendingIntent; +import android.app.Service; +import android.appwidget.AppWidgetManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.media.audiofx.AudioEffect; +import android.media.session.MediaSession; +import android.os.Binder; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.widget.Toast; + +import code.name.monkey.retromusic.service.notification.PlayingNotificationOreo; +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appwidgets.AppWidgetBig; +import code.name.monkey.retromusic.appwidgets.AppWidgetCard; +import code.name.monkey.retromusic.appwidgets.AppWidgetClassic; +import code.name.monkey.retromusic.appwidgets.AppWidgetSmall; +import code.name.monkey.retromusic.glide.BlurTransformation; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.ShuffleHelper; +import code.name.monkey.retromusic.helper.StopWatch; +import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; +import code.name.monkey.retromusic.model.AbsCustomPlaylist; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.providers.HistoryStore; +import code.name.monkey.retromusic.providers.MusicPlaybackQueueStore; +import code.name.monkey.retromusic.providers.SongPlayCountStore; +import code.name.monkey.retromusic.service.notification.PlayingNotification; +import code.name.monkey.retromusic.service.notification.PlayingNotificationImpl24; +import code.name.monkey.retromusic.service.playback.Playback; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +import static code.name.monkey.retromusic.Constants.ACTION_PAUSE; +import static code.name.monkey.retromusic.Constants.ACTION_PLAY; +import static code.name.monkey.retromusic.Constants.ACTION_PLAY_PLAYLIST; +import static code.name.monkey.retromusic.Constants.ACTION_QUIT; +import static code.name.monkey.retromusic.Constants.ACTION_REWIND; +import static code.name.monkey.retromusic.Constants.ACTION_SKIP; +import static code.name.monkey.retromusic.Constants.ACTION_STOP; +import static code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE; +import static code.name.monkey.retromusic.Constants.APP_WIDGET_UPDATE; +import static code.name.monkey.retromusic.Constants.EXTRA_APP_WIDGET_NAME; +import static code.name.monkey.retromusic.Constants.INTENT_EXTRA_PLAYLIST; +import static code.name.monkey.retromusic.Constants.INTENT_EXTRA_SHUFFLE_MODE; +import static code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED; +import static code.name.monkey.retromusic.Constants.META_CHANGED; +import static code.name.monkey.retromusic.Constants.MUSIC_PACKAGE_NAME; +import static code.name.monkey.retromusic.Constants.PLAY_STATE_CHANGED; +import static code.name.monkey.retromusic.Constants.QUEUE_CHANGED; +import static code.name.monkey.retromusic.Constants.REPEAT_MODE_CHANGED; +import static code.name.monkey.retromusic.Constants.RETRO_MUSIC_PACKAGE_NAME; +import static code.name.monkey.retromusic.Constants.SHUFFLE_MODE_CHANGED; + +/** + * @author Karim Abou Zeid (kabouzeid), Andrew Neal + */ +public class MusicService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener, Playback.PlaybackCallbacks { + public static final String TAG = MusicService.class.getSimpleName(); + + public static final String SAVED_POSITION = "POSITION"; + public static final String SAVED_POSITION_IN_TRACK = "POSITION_IN_TRACK"; + public static final String SAVED_SHUFFLE_MODE = "SHUFFLE_MODE"; + public static final String SAVED_REPEAT_MODE = "REPEAT_MODE"; + + public static final int RELEASE_WAKELOCK = 0; + public static final int TRACK_ENDED = 1; + public static final int TRACK_WENT_TO_NEXT = 2; + public static final int PLAY_SONG = 3; + public static final int PREPARE_NEXT = 4; + public static final int SET_POSITION = 5; + public static final int RESTORE_QUEUES = 9; + 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; + public static final int SAVE_QUEUES = 0; + private static final int FOCUS_CHANGE = 6; + private static final int DUCK = 7; + private static final int UNDUCK = 8; + private static final long MEDIA_SESSION_ACTIONS = PlaybackStateCompat.ACTION_PLAY + | PlaybackStateCompat.ACTION_PAUSE + | PlaybackStateCompat.ACTION_PLAY_PAUSE + | PlaybackStateCompat.ACTION_SKIP_TO_NEXT + | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + | PlaybackStateCompat.ACTION_STOP + | PlaybackStateCompat.ACTION_SEEK_TO; + private final IBinder musicBind = new MusicBinder(); + private AppWidgetBig appWidgetBig = AppWidgetBig.getInstance(); + private AppWidgetClassic appWidgetClassic = AppWidgetClassic.getInstance(); + private AppWidgetSmall appWidgetSmall = AppWidgetSmall.getInstance(); + private AppWidgetCard appWidgetCard = AppWidgetCard.getInstance(); + private Playback playback; + private ArrayList playingQueue = new ArrayList<>(); + private ArrayList originalPlayingQueue = new ArrayList<>(); + private int position = -1; + private int nextPosition = -1; + private int shuffleMode; + private int repeatMode; + private boolean queuesRestored; + private boolean pausedByTransientLossOfFocus; + private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, @NonNull Intent intent) { + if (intent.getAction() != null && intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + pause(); + } + } + }; + private PlayingNotification playingNotification; + private AudioManager audioManager; + @SuppressWarnings("deprecation") + private MediaSessionCompat mediaSession; + private PowerManager.WakeLock wakeLock; + private PlaybackHandler playerHandler; + private final AudioManager.OnAudioFocusChangeListener audioFocusListener = new AudioManager.OnAudioFocusChangeListener() { + @Override + public void onAudioFocusChange(final int focusChange) { + playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget(); + } + }; + private QueueSaveHandler queueSaveHandler; + private HandlerThread musicPlayerHandlerThread; + private HandlerThread queueSaveHandlerThread; + private SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); + private ThrottledSeekHandler throttledSeekHandler; + private boolean becomingNoisyReceiverRegistered; + private IntentFilter becomingNoisyReceiverIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + private ContentObserver mediaStoreObserver; + private boolean notHandledMetaChangedForCurrentTrack; + private PhoneStateListener phoneStateListener = new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + //Not in call: Play music + play(); + break; + case TelephonyManager.CALL_STATE_RINGING: + case TelephonyManager.CALL_STATE_OFFHOOK: + //A call is dialing, active or on hold + pause(); + break; + default: + } + super.onCallStateChanged(state, incomingNumber); + } + }; + private boolean isServiceBound; + private Handler uiThreadHandler; + private final BroadcastReceiver widgetIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + final String command = intent.getStringExtra(EXTRA_APP_WIDGET_NAME); + + if (AppWidgetClassic.NAME.equals(command)) { + final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + appWidgetClassic.performUpdate(MusicService.this, ids); + } else if (AppWidgetSmall.NAME.equals(command)) { + final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + appWidgetSmall.performUpdate(MusicService.this, ids); + } else if (AppWidgetBig.NAME.equals(command)) { + final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + appWidgetBig.performUpdate(MusicService.this, ids); + } else if (AppWidgetCard.NAME.equals(command)) { + final int[] ids = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + appWidgetCard.performUpdate(MusicService.this, ids); + } + } + }; + private IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + private boolean headsetReceiverRegistered = false; + private BroadcastReceiver headsetReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null) { + switch (action) { + case Intent.ACTION_HEADSET_PLUG: + int state = intent.getIntExtra("state", -1); + switch (state) { + case 0: + Log.d(TAG, "Headset unplugged"); + pause(); + break; + case 1: + Log.d(TAG, "Headset plugged"); + play(); + break; + } + break; + } + } + } + }; + + private static String getTrackUri(@NonNull Song song) { + return MusicUtil.getSongFileUri(song.id).toString(); + } + + private static Bitmap copy(Bitmap bitmap) { + Bitmap.Config config = bitmap.getConfig(); + if (config == null) { + config = Bitmap.Config.RGB_565; + } + try { + return bitmap.copy(config, false); + } catch (OutOfMemoryError e) { + e.printStackTrace(); + return null; + } + } + + + @Override + public void onCreate() { + super.onCreate(); + + final TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); + if (telephonyManager != null) { + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); + } + + final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); + if (powerManager != null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); + } + wakeLock.setReferenceCounted(false); + + musicPlayerHandlerThread = new HandlerThread("PlaybackHandler"); + musicPlayerHandlerThread.start(); + playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper()); + + playback = new MultiPlayer(this); + playback.setCallbacks(this); + + setupMediaSession(); + + // queue saving needs to run on a separate thread so that it doesn't block the playback handler events + queueSaveHandlerThread = new HandlerThread("QueueSaveHandler", Process.THREAD_PRIORITY_BACKGROUND); + queueSaveHandlerThread.start(); + queueSaveHandler = new QueueSaveHandler(this, queueSaveHandlerThread.getLooper()); + + uiThreadHandler = new Handler(); + + registerReceiver(widgetIntentReceiver, new IntentFilter(APP_WIDGET_UPDATE)); + + initNotification(); + + mediaStoreObserver = new MediaStoreObserver(playerHandler); + throttledSeekHandler = new ThrottledSeekHandler(playerHandler); + getContentResolver().registerContentObserver( + MediaStore.Audio.Media.INTERNAL_CONTENT_URI, true, mediaStoreObserver); + getContentResolver().registerContentObserver( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, mediaStoreObserver); + + PreferenceUtil.getInstance(this).registerOnSharedPreferenceChangedListener(this); + + restoreState(); + + mediaSession.setActive(true); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_SERVICE_CREATED")); + + registerHeadsetEvents(); + + } + + private AudioManager getAudioManager() { + if (audioManager == null) { + audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + } + return audioManager; + } + + private void setupMediaSession() { + ComponentName mediaButtonReceiverComponentName = new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class); + + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); + + + PendingIntent mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + + mediaSession = new MediaSessionCompat(this, "RetroMusicPlayer", mediaButtonReceiverComponentName, mediaButtonReceiverPendingIntent); + mediaSession.setCallback(new MediaSessionCompat.Callback() { + @Override + public void onPlay() { + play(); + } + + @Override + public void onPause() { + pause(); + } + + @Override + public void onSkipToNext() { + playNextSong(true); + } + + @Override + public void onSkipToPrevious() { + back(true); + } + + @Override + public void onStop() { + quit(); + } + + @Override + public void onSeekTo(long pos) { + seek((int) pos); + } + + @Override + public boolean onMediaButtonEvent(Intent mediaButtonEvent) { + return MediaButtonIntentReceiver.handleIntent(MusicService.this, mediaButtonEvent); + } + }); + + mediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS + | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS); + + mediaSession.setMediaButtonReceiver(mediaButtonReceiverPendingIntent); + } + + @Override + public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + if (intent != null) { + if (intent.getAction() != null) { + restoreQueuesAndPositionIfNecessary(); + String action = intent.getAction(); + switch (action) { + case ACTION_TOGGLE_PAUSE: + if (isPlaying()) { + pause(); + } else { + play(); + } + break; + case ACTION_PAUSE: + pause(); + break; + case ACTION_PLAY: + play(); + break; + case ACTION_PLAY_PLAYLIST: + Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST); + int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode()); + if (playlist != null) { + ArrayList playlistSongs; + if (playlist instanceof AbsCustomPlaylist) { + playlistSongs = ((AbsCustomPlaylist) playlist).getSongs(getApplicationContext()).blockingFirst(); + } else { + //noinspection unchecked + playlistSongs = PlaylistSongsLoader.getPlaylistSongList(getApplicationContext(), playlist.id).blockingFirst(); + } + if (!playlistSongs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition; + startPosition = new Random().nextInt(playlistSongs.size()); + openQueue(playlistSongs, startPosition, true); + setShuffleMode(shuffleMode); + } else { + openQueue(playlistSongs, 0, true); + } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + break; + case ACTION_REWIND: + back(true); + break; + case ACTION_SKIP: + playNextSong(true); + break; + case ACTION_STOP: + case ACTION_QUIT: + return quit(); + } + } + } + + return START_STICKY; + } + + + @Override + public void onDestroy() { + unregisterReceiver(widgetIntentReceiver); + if (becomingNoisyReceiverRegistered) { + unregisterReceiver(becomingNoisyReceiver); + becomingNoisyReceiverRegistered = false; + } + if (headsetReceiverRegistered) { + unregisterReceiver(headsetReceiver); + headsetReceiverRegistered = false; + } + mediaSession.setActive(false); + quit(); + releaseResources(); + getContentResolver().unregisterContentObserver(mediaStoreObserver); + PreferenceUtil.getInstance(this).unregisterOnSharedPreferenceChangedListener(this); + wakeLock.release(); + + sendBroadcast(new Intent("code.name.monkey.retromusic.RETRO_MUSIC_MUSIC_SERVICE_DESTROYED")); + } + + @Override + public IBinder onBind(Intent intent) { + isServiceBound = true; + return musicBind; + } + + @Override + public void onRebind(Intent intent) { + isServiceBound = true; + } + + @Override + public boolean onUnbind(Intent intent) { + isServiceBound = false; + if (!isPlaying()) { + stopSelf(); + } + return true; + } + + private void saveQueuesImpl() { + MusicPlaybackQueueStore.getInstance(this).saveQueues(playingQueue, originalPlayingQueue); + } + + private void savePosition() { + PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION, getPosition()).apply(); + } + + private void savePositionInTrack() { + PreferenceManager.getDefaultSharedPreferences(this).edit().putInt(SAVED_POSITION_IN_TRACK, getSongProgressMillis()).apply(); + } + + public void saveState() { + saveQueues(); + savePosition(); + savePositionInTrack(); + } + + private void saveQueues() { + queueSaveHandler.removeMessages(SAVE_QUEUES); + queueSaveHandler.sendEmptyMessage(SAVE_QUEUES); + } + + private void restoreState() { + shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_SHUFFLE_MODE, 0); + repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_REPEAT_MODE, 0); + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + + playerHandler.removeMessages(RESTORE_QUEUES); + playerHandler.sendEmptyMessage(RESTORE_QUEUES); + } + + private synchronized void restoreQueuesAndPositionIfNecessary() { + if (!queuesRestored && playingQueue.isEmpty()) { + ArrayList restoredQueue = MusicPlaybackQueueStore.getInstance(this).getSavedPlayingQueue() + .blockingFirst(); + + ArrayList restoredOriginalQueue = MusicPlaybackQueueStore.getInstance(this).getSavedOriginalPlayingQueue() + .blockingFirst(); + + int restoredPosition = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION, -1); + int restoredPositionInTrack = PreferenceManager.getDefaultSharedPreferences(this).getInt(SAVED_POSITION_IN_TRACK, -1); + + if (restoredQueue.size() > 0 && restoredQueue.size() == restoredOriginalQueue.size() && restoredPosition != -1) { + this.originalPlayingQueue = restoredOriginalQueue; + this.playingQueue = restoredQueue; + + position = restoredPosition; + openCurrent(); + prepareNext(); + + if (restoredPositionInTrack > 0) seek(restoredPositionInTrack); + + notHandledMetaChangedForCurrentTrack = true; + sendChangeInternal(META_CHANGED); + sendChangeInternal(QUEUE_CHANGED); + } + } + queuesRestored = true; + } + + private int quit() { + pause(); + playingNotification.stop(); + + if (isServiceBound) { + return START_STICKY; + } else { + closeAudioEffectSession(); + getAudioManager().abandonAudioFocus(audioFocusListener); + stopSelf(); + return START_NOT_STICKY; + } + } + + private void releaseResources() { + playerHandler.removeCallbacksAndMessages(null); + musicPlayerHandlerThread.quitSafely(); + queueSaveHandler.removeCallbacksAndMessages(null); + queueSaveHandlerThread.quitSafely(); + playback.release(); + playback = null; + mediaSession.release(); + } + + public boolean isPlaying() { + return playback != null && playback.isPlaying(); + } + + public int getPosition() { + return position; + } + + public void setPosition(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(SET_POSITION); + playerHandler.obtainMessage(SET_POSITION, position, 0).sendToTarget(); + } + + public void playNextSong(boolean force) { + playSongAt(getNextPosition(force)); + } + + private boolean openTrackAndPrepareNextAt(int position) { + synchronized (this) { + this.position = position; + boolean prepared = openCurrent(); + if (prepared) prepareNextImpl(); + notifyChange(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + return prepared; + } + } + + private boolean openCurrent() { + synchronized (this) { + try { + return playback.setDataSource(getTrackUri(getCurrentSong())); + } catch (Exception e) { + return false; + } + } + } + + private void prepareNext() { + playerHandler.removeMessages(PREPARE_NEXT); + playerHandler.obtainMessage(PREPARE_NEXT).sendToTarget(); + } + + private boolean prepareNextImpl() { + synchronized (this) { + try { + int nextPosition = getNextPosition(false); + playback.setNextDataSource(getTrackUri(getSongAt(nextPosition))); + this.nextPosition = nextPosition; + return true; + } catch (Exception e) { + return false; + } + } + } + + private void closeAudioEffectSession() { + final Intent audioEffectsIntent = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + audioEffectsIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playback.getAudioSessionId()); + audioEffectsIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); + sendBroadcast(audioEffectsIntent); + } + + private boolean requestFocus() { + return (getAudioManager().requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); + } + + public void initNotification() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !PreferenceUtil.getInstance(this).classicNotification()) { + playingNotification = new PlayingNotificationImpl24(); + } else { + playingNotification = new PlayingNotificationOreo(); + } + playingNotification.init(this); + } + + public void updateNotification() { + if (playingNotification != null && getCurrentSong().id != -1) { + playingNotification.update(); + } + } + + private void updateMediaSessionPlaybackState() { + mediaSession.setPlaybackState( + new PlaybackStateCompat.Builder() + .setActions(MEDIA_SESSION_ACTIONS) + .setState(isPlaying() ? PlaybackStateCompat.STATE_PLAYING : PlaybackStateCompat.STATE_PAUSED, + getPosition(), 1) + .build()); + } + + private void updateMediaSessionMetaData() { + final Song song = getCurrentSong(); + + if (song.id == -1) { + mediaSession.setMetadata(null); + return; + } + + final MediaMetadataCompat.Builder metaData = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.artistName) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName) + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) + .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) + .putLong(MediaMetadataCompat.METADATA_KEY_YEAR, song.year) + .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, null); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + metaData.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()); + } + + if (PreferenceUtil.getInstance(this).albumArtOnLockscreen()) { + final Point screenSize = RetroUtil.getScreenSize(MusicService.this); + final BitmapRequestBuilder request = SongGlideRequest.Builder.from(Glide.with(MusicService.this), song) + .checkIgnoreMediaStore(MusicService.this) + .asBitmap().build(); + if (PreferenceUtil.getInstance(this).blurredAlbumArt()) { + request.transform(new BlurTransformation.Builder(MusicService.this).build()); + } + runOnUiThread(new Runnable() { + @Override + public void run() { + request.into(new SimpleTarget(screenSize.x, screenSize.y) { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + mediaSession.setMetadata(metaData.build()); + } + + @Override + public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { + metaData.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, copy(resource)); + mediaSession.setMetadata(metaData.build()); + } + }); + } + }); + } else { + mediaSession.setMetadata(metaData.build()); + } + } + + public void runOnUiThread(Runnable runnable) { + uiThreadHandler.post(runnable); + } + + public Song getCurrentSong() { + return getSongAt(getPosition()); + } + + public Song getSongAt(int position) { + if (position >= 0 && position < getPlayingQueue().size()) { + return getPlayingQueue().get(position); + } else { + return Song.EMPTY_SONG; + } + } + + public int getNextPosition(boolean force) { + int position = getPosition() + 1; + switch (getRepeatMode()) { + case REPEAT_MODE_ALL: + if (isLastTrack()) { + position = 0; + } + break; + case REPEAT_MODE_THIS: + if (force) { + if (isLastTrack()) { + position = 0; + } + } else { + position -= 1; + } + break; + default: + case REPEAT_MODE_NONE: + if (isLastTrack()) { + position -= 1; + } + break; + } + return position; + } + + private boolean isLastTrack() { + return getPosition() == getPlayingQueue().size() - 1; + } + + public ArrayList getPlayingQueue() { + return playingQueue; + } + + public int getRepeatMode() { + return repeatMode; + } + + 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(SAVED_REPEAT_MODE, repeatMode) + .apply(); + prepareNext(); + handleAndSendChangeInternal(REPEAT_MODE_CHANGED); + break; + } + } + + public void openQueue(@Nullable final ArrayList playingQueue, final int startPosition, final boolean startPlaying) { + if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) { + // it is important to copy the playing queue here first as we might add/remove songs later + originalPlayingQueue = new ArrayList<>(playingQueue); + this.playingQueue = new ArrayList<>(originalPlayingQueue); + + int position = startPosition; + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + ShuffleHelper.makeShuffleList(this.playingQueue, startPosition); + position = 0; + } + if (startPlaying) { + playSongAt(position); + } else { + setPosition(position); + } + notifyChange(QUEUE_CHANGED); + } + } + + public void addSong(int position, Song song) { + playingQueue.add(position, song); + originalPlayingQueue.add(position, song); + notifyChange(QUEUE_CHANGED); + } + + public void addSong(Song song) { + playingQueue.add(song); + originalPlayingQueue.add(song); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(int position, List songs) { + playingQueue.addAll(position, songs); + originalPlayingQueue.addAll(position, songs); + notifyChange(QUEUE_CHANGED); + } + + public void addSongs(List songs) { + playingQueue.addAll(songs); + originalPlayingQueue.addAll(songs); + notifyChange(QUEUE_CHANGED); + } + + public void removeSong(int position) { + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + playingQueue.remove(position); + originalPlayingQueue.remove(position); + } else { + originalPlayingQueue.remove(playingQueue.remove(position)); + } + + rePosition(position); + + notifyChange(QUEUE_CHANGED); + } + + public void removeSong(@NonNull Song song) { + for (int i = 0; i < playingQueue.size(); i++) { + if (playingQueue.get(i).id == song.id) { + playingQueue.remove(i); + rePosition(i); + } + } + for (int i = 0; i < originalPlayingQueue.size(); i++) { + if (originalPlayingQueue.get(i).id == song.id) { + originalPlayingQueue.remove(i); + } + } + notifyChange(QUEUE_CHANGED); + } + + private void rePosition(int deletedPosition) { + int currentPosition = getPosition(); + if (deletedPosition < currentPosition) { + position = currentPosition - 1; + } else if (deletedPosition == currentPosition) { + if (playingQueue.size() > deletedPosition) { + setPosition(position); + } else { + setPosition(position - 1); + } + } + } + + public void moveSong(int from, int to) { + if (from == to) return; + final int currentPosition = getPosition(); + Song songToMove = playingQueue.remove(from); + playingQueue.add(to, songToMove); + if (getShuffleMode() == SHUFFLE_MODE_NONE) { + Song tmpSong = originalPlayingQueue.remove(from); + originalPlayingQueue.add(to, tmpSong); + } + if (from > currentPosition && to <= currentPosition) { + position = currentPosition + 1; + } else if (from < currentPosition && to >= currentPosition) { + position = currentPosition - 1; + } else if (from == currentPosition) { + position = to; + } + notifyChange(QUEUE_CHANGED); + } + + public void clearQueue() { + playingQueue.clear(); + originalPlayingQueue.clear(); + + setPosition(-1); + notifyChange(QUEUE_CHANGED); + } + + public void playSongAt(final int position) { + // handle this on the handlers thread to avoid blocking the ui thread + playerHandler.removeMessages(PLAY_SONG); + playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget(); + } + + private void playSongAtImpl(int position) { + if (openTrackAndPrepareNextAt(position)) { + play(); + } else { + Toast.makeText(this, getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show(); + } + } + + public void pause() { + pausedByTransientLossOfFocus = false; + if (playback.isPlaying()) { + playback.pause(); + notifyChange(PLAY_STATE_CHANGED); + } + } + + public void play() { + synchronized (this) { + if (requestFocus()) { + if (!playback.isPlaying()) { + if (!playback.isInitialized()) { + playSongAt(getPosition()); + } else { + playback.start(); + if (!becomingNoisyReceiverRegistered) { + registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter); + becomingNoisyReceiverRegistered = true; + } + if (notHandledMetaChangedForCurrentTrack) { + handleChangeInternal(META_CHANGED); + notHandledMetaChangedForCurrentTrack = false; + } + notifyChange(PLAY_STATE_CHANGED); + + // fixes a bug where the volume would stay ducked because the AudioManager.AUDIOFOCUS_GAIN event is not sent + playerHandler.removeMessages(DUCK); + playerHandler.sendEmptyMessage(UNDUCK); + } + } + } else { + Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show(); + } + } + } + + public void playSongs(ArrayList songs, int shuffleMode) { + if (songs != null && !songs.isEmpty()) { + if (shuffleMode == SHUFFLE_MODE_SHUFFLE) { + int startPosition = 0; + if (!songs.isEmpty()) { + startPosition = new Random().nextInt(songs.size()); + } + openQueue(songs, startPosition, false); + setShuffleMode(shuffleMode); + } else { + openQueue(songs, 0, false); + } + play(); + } else { + Toast.makeText(getApplicationContext(), R.string.playlist_is_empty, Toast.LENGTH_LONG).show(); + } + } + + public void playPreviousSong(boolean force) { + playSongAt(getPreviousPosition(force)); + } + + public void back(boolean force) { + if (getSongProgressMillis() > 2000) { + seek(0); + } else { + playPreviousSong(force); + } + } + + public int getPreviousPosition(boolean force) { + int newPosition = getPosition() - 1; + switch (repeatMode) { + case REPEAT_MODE_ALL: + if (newPosition < 0) { + newPosition = getPlayingQueue().size() - 1; + } + break; + case REPEAT_MODE_THIS: + if (force) { + if (newPosition < 0) { + newPosition = getPlayingQueue().size() - 1; + } + } else { + newPosition = getPosition(); + } + break; + default: + case REPEAT_MODE_NONE: + if (newPosition < 0) { + newPosition = 0; + } + break; + } + return newPosition; + } + + public int getSongProgressMillis() { + return playback.position(); + } + + public int getSongDurationMillis() { + return playback.duration(); + } + + public long getQueueDurationMillis(int position) { + long duration = 0; + for (int i = position + 1; i < playingQueue.size(); i++) + duration += playingQueue.get(i).duration; + return duration; + } + + public int seek(int millis) { + synchronized (this) { + try { + int newPosition = playback.seek(millis); + throttledSeekHandler.notifySeek(); + return newPosition; + } catch (Exception e) { + return -1; + } + } + } + + public void cycleRepeatMode() { + switch (getRepeatMode()) { + 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 (getShuffleMode() == SHUFFLE_MODE_NONE) { + setShuffleMode(SHUFFLE_MODE_SHUFFLE); + } else { + setShuffleMode(SHUFFLE_MODE_NONE); + } + } + + public int getShuffleMode() { + return shuffleMode; + } + + public void setShuffleMode(final int shuffleMode) { + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putInt(SAVED_SHUFFLE_MODE, shuffleMode) + .apply(); + switch (shuffleMode) { + case SHUFFLE_MODE_SHUFFLE: + this.shuffleMode = shuffleMode; + ShuffleHelper.makeShuffleList(this.getPlayingQueue(), getPosition()); + position = 0; + break; + case SHUFFLE_MODE_NONE: + this.shuffleMode = shuffleMode; + int currentSongId = getCurrentSong().id; + playingQueue = new ArrayList<>(originalPlayingQueue); + int newPosition = 0; + for (Song song : getPlayingQueue()) { + if (song.id == currentSongId) { + newPosition = getPlayingQueue().indexOf(song); + } + } + position = newPosition; + break; + } + handleAndSendChangeInternal(SHUFFLE_MODE_CHANGED); + notifyChange(QUEUE_CHANGED); + } + + private void notifyChange(@NonNull final String what) { + handleAndSendChangeInternal(what); + sendPublicIntent(what); + } + + private void handleAndSendChangeInternal(@NonNull final String what) { + handleChangeInternal(what); + sendChangeInternal(what); + } + + // to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch + private void sendPublicIntent(@NonNull final String what) { + final Intent intent = new Intent(what.replace(RETRO_MUSIC_PACKAGE_NAME, MUSIC_PACKAGE_NAME)); + + final Song song = getCurrentSong(); + + intent.putExtra("id", song.id); + + intent.putExtra("artist", song.artistName); + intent.putExtra("album", song.albumName); + intent.putExtra("track", song.title); + + intent.putExtra("duration", song.duration); + intent.putExtra("position", (long) getSongProgressMillis()); + + intent.putExtra("playing", isPlaying()); + + intent.putExtra("scrobbling_source", RETRO_MUSIC_PACKAGE_NAME); + + sendStickyBroadcast(intent); + + } + + private void sendChangeInternal(final String what) { + sendBroadcast(new Intent(what)); + appWidgetBig.notifyChange(this, what); + appWidgetClassic.notifyChange(this, what); + appWidgetSmall.notifyChange(this, what); + appWidgetCard.notifyChange(this, what); + } + + private void handleChangeInternal(@NonNull final String what) { + switch (what) { + case PLAY_STATE_CHANGED: + updateNotification(); + updateMediaSessionPlaybackState(); + final boolean isPlaying = isPlaying(); + if (!isPlaying && getSongProgressMillis() > 0) { + savePositionInTrack(); + } + songPlayCountHelper.notifyPlayStateChanged(isPlaying); + break; + case META_CHANGED: + updateNotification(); + updateMediaSessionMetaData(); + savePosition(); + savePositionInTrack(); + final Song currentSong = getCurrentSong(); + HistoryStore.getInstance(this).addSongId(currentSong.id); + if (songPlayCountHelper.shouldBumpPlayCount()) { + SongPlayCountStore.getInstance(this).bumpPlayCount(songPlayCountHelper.getSong().id); + } + songPlayCountHelper.notifySongChanged(currentSong); + break; + case QUEUE_CHANGED: + updateMediaSessionMetaData(); // because playing queue size might have changed + saveState(); + if (playingQueue.size() > 0) { + prepareNext(); + } else { + playingNotification.stop(); + } + break; + } + } + + public int getAudioSessionId() { + return playback.getAudioSessionId(); + } + + public MediaSessionCompat getMediaSession() { + return mediaSession; + } + + public void releaseWakeLock() { + if (wakeLock.isHeld()) { + wakeLock.release(); + } + } + + public void acquireWakeLock(long milli) { + wakeLock.acquire(milli); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case PreferenceUtil.GAPLESS_PLAYBACK: + if (sharedPreferences.getBoolean(key, false)) { + prepareNext(); + } else { + playback.setNextDataSource(null); + } + break; + case PreferenceUtil.ALBUM_ART_ON_LOCKSCREEN: + case PreferenceUtil.BLURRED_ALBUM_ART: + updateMediaSessionMetaData(); + break; + case PreferenceUtil.COLORED_NOTIFICATION: + case PreferenceUtil.DOMINANT_COLOR: + updateNotification(); + break; + case PreferenceUtil.CLASSIC_NOTIFICATION: + initNotification(); + updateNotification(); + break; + case PreferenceUtil.TOGGLE_HEADSET: + registerHeadsetEvents(); + break; + } + } + + private void registerHeadsetEvents() { + if (!headsetReceiverRegistered && PreferenceUtil.getInstance(this).getHeadsetPlugged()) { + registerReceiver(headsetReceiver, headsetReceiverIntentFilter); + headsetReceiverRegistered = true; + } + } + + @Override + public void onTrackWentToNext() { + playerHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT); + } + + @Override + public void onTrackEnded() { + acquireWakeLock(30000); + playerHandler.sendEmptyMessage(TRACK_ENDED); + } + + private static final class QueueSaveHandler extends Handler { + @NonNull + private final WeakReference mService; + + QueueSaveHandler(final MusicService service, @NonNull final Looper looper) { + super(looper); + mService = new WeakReference<>(service); + } + + @Override + public void handleMessage(@NonNull Message msg) { + final MusicService service = mService.get(); + switch (msg.what) { + case SAVE_QUEUES: + service.saveQueuesImpl(); + break; + } + } + } + + private static final class PlaybackHandler extends Handler { + @NonNull + private final WeakReference mService; + private float currentDuckVolume = 1.0f; + + PlaybackHandler(final MusicService service, @NonNull final Looper looper) { + super(looper); + mService = new WeakReference<>(service); + } + + @Override + public void handleMessage(@NonNull final Message msg) { + final MusicService service = mService.get(); + if (service == null) { + return; + } + + switch (msg.what) { + case DUCK: + if (PreferenceUtil.getInstance(service).audioDucking()) { + currentDuckVolume -= .05f; + if (currentDuckVolume > .2f) { + sendEmptyMessageDelayed(DUCK, 10); + } else { + currentDuckVolume = .2f; + } + } else { + currentDuckVolume = 1f; + } + service.playback.setVolume(currentDuckVolume); + break; + + case UNDUCK: + if (PreferenceUtil.getInstance(service).audioDucking()) { + currentDuckVolume += .03f; + if (currentDuckVolume < 1f) { + sendEmptyMessageDelayed(UNDUCK, 10); + } else { + currentDuckVolume = 1f; + } + } else { + currentDuckVolume = 1f; + } + service.playback.setVolume(currentDuckVolume); + break; + + case TRACK_WENT_TO_NEXT: + if (service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { + service.pause(); + service.seek(0); + } else { + service.position = service.nextPosition; + service.prepareNextImpl(); + service.notifyChange(META_CHANGED); + } + break; + + case TRACK_ENDED: + if (service.getRepeatMode() == REPEAT_MODE_NONE && service.isLastTrack()) { + service.notifyChange(PLAY_STATE_CHANGED); + service.seek(0); + } else { + service.playNextSong(false); + } + sendEmptyMessage(RELEASE_WAKELOCK); + break; + + case RELEASE_WAKELOCK: + service.releaseWakeLock(); + break; + + case PLAY_SONG: + service.playSongAtImpl(msg.arg1); + break; + + case SET_POSITION: + service.openTrackAndPrepareNextAt(msg.arg1); + service.notifyChange(PLAY_STATE_CHANGED); + break; + + case PREPARE_NEXT: + service.prepareNextImpl(); + break; + + case RESTORE_QUEUES: + service.restoreQueuesAndPositionIfNecessary(); + break; + + case FOCUS_CHANGE: + switch (msg.arg1) { + case AudioManager.AUDIOFOCUS_GAIN: + if (!service.isPlaying() && service.pausedByTransientLossOfFocus) { + service.play(); + service.pausedByTransientLossOfFocus = false; + } + removeMessages(DUCK); + sendEmptyMessage(UNDUCK); + break; + + case AudioManager.AUDIOFOCUS_LOSS: + // Lost focus for an unbounded amount of time: stop playback and release media playback + service.pause(); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + // Lost focus for a short time, but we have to stop + // playback. We don't release the media playback because playback + // is likely to resume + boolean wasPlaying = service.isPlaying(); + service.pause(); + service.pausedByTransientLossOfFocus = wasPlaying; + 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 + removeMessages(UNDUCK); + sendEmptyMessage(DUCK); + break; + } + break; + } + } + } + + private static class SongPlayCountHelper { + public static final String TAG = SongPlayCountHelper.class.getSimpleName(); + + private StopWatch stopWatch = new StopWatch(); + private Song song = Song.EMPTY_SONG; + + public Song getSong() { + return song; + } + + boolean shouldBumpPlayCount() { + return song.duration * 0.5d < stopWatch.getElapsedTime(); + } + + void notifySongChanged(Song song) { + synchronized (this) { + stopWatch.reset(); + this.song = song; + } + } + + void notifyPlayStateChanged(boolean isPlaying) { + synchronized (this) { + if (isPlaying) { + stopWatch.start(); + } else { + stopWatch.pause(); + } + } + } + } + + public class MusicBinder extends Binder { + @NonNull + public MusicService getService() { + return MusicService.this; + } + } + + private class MediaStoreObserver extends ContentObserver implements Runnable { + // milliseconds to delay before calling refresh to aggregate events + private static final long REFRESH_DELAY = 500; + private Handler mHandler; + + MediaStoreObserver(Handler handler) { + super(handler); + mHandler = handler; + } + + @Override + public void onChange(boolean selfChange) { + // if a change is detected, remove any scheduled callback + // then post a new one. This is intended to prevent closely + // spaced events from generating multiple refresh calls + mHandler.removeCallbacks(this); + mHandler.postDelayed(this, REFRESH_DELAY); + } + + @Override + public void run() { + // actually call refresh when the delayed callback fires + // do not send a sticky broadcast here + handleAndSendChangeInternal(MEDIA_STORE_CHANGED); + } + } + + private class ThrottledSeekHandler implements Runnable { + // milliseconds to throttle before calling run() to aggregate events + private static final long THROTTLE = 500; + private Handler mHandler; + + ThrottledSeekHandler(Handler handler) { + mHandler = handler; + } + + void notifySeek() { + mHandler.removeCallbacks(this); + mHandler.postDelayed(this, THROTTLE); + } + + @Override + public void run() { + savePositionInTrack(); + sendPublicIntent(PLAY_STATE_CHANGED); // for musixmatch synced lyrics + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/service/WearBrowserService.java b/app/src/main/java/code/name/monkey/retromusic/service/WearBrowserService.java new file mode 100644 index 00000000..5bf23cfe --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/WearBrowserService.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2015 Naman Dwivedi + * + * Licensed under the GNU General Public License v3 + * + * This is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + */ +package code.name.monkey.retromusic.service; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.media.MediaDescription; +import android.media.browse.MediaBrowser; +import android.media.session.MediaSession; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.service.media.MediaBrowserService; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.RetroUtil; + +/** + * @author Hemanth S (h4h13). + */ +@TargetApi(21) +public class WearBrowserService extends MediaBrowserService { + + public static final String MEDIA_ID_ROOT = "__ROOT__"; + public static final int TYPE_ARTIST = 0; + public static final int TYPE_ALBUM = 1; + public static final int TYPE_SONG = 2; + public static final int TYPE_PLAYLIST = 3; + public static final int TYPE_ARTIST_SONG_ALBUMS = 4; + public static final int TYPE_ALBUM_SONGS = 5; + public static final int TYPE_ARTIST_ALL_SONGS = 6; + public static final int TYPE_PLAYLIST_ALL_SONGS = 7; + + public static WearBrowserService sInstance; + MediaSession mSession; + private Context mContext; + private boolean mServiceStarted; + + public static WearBrowserService getInstance() { + return sInstance; + } + + @Override + public void onCreate() { + super.onCreate(); + sInstance = this; + mContext = this; + mSession = new MediaSession(this, "WearBrowserService"); + setSessionToken(mSession.getSessionToken()); + mSession.setCallback(new MediaSessionCallback()); + mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + + } + + @Override + public int onStartCommand(Intent startIntent, int flags, int startId) { + return START_STICKY; + } + + @Override + public void onDestroy() { + mServiceStarted = false; + mSession.release(); + } + + @Nullable + @Override + public BrowserRoot onGetRoot(@NonNull String s, int i, @Nullable Bundle bundle) { + return new BrowserRoot(MEDIA_ID_ROOT, null); + } + + @Override + public void onLoadChildren(@NonNull String parentId, @NonNull Result> result) { + result.detach(); + loadChildren(parentId, result); + } + + private void setSessionActive() { + if (!mServiceStarted) { + startService(new Intent(getApplicationContext(), WearBrowserService.class)); + mServiceStarted = true; + } + + if (!mSession.isActive()) { + mSession.setActive(true); + } + } + + private void setSessionInactive() { + if (mServiceStarted) { + stopSelf(); + mServiceStarted = false; + } + + if (mSession.isActive()) { + mSession.setActive(false); + } + } + + private void fillMediaItems(List mediaItems, String mediaId, + String title, Uri icon, String subTitle, int playableOrBrowsable) { + mediaItems.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(mediaId) + .setTitle(title) + .setIconUri(icon) + .setSubtitle(subTitle) + .build(), playableOrBrowsable + )); + } + + private void addMediaRoots(List mMediaRoot) { + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_ARTIST)) + .setTitle(getString(R.string.artists)) + .setIconUri(Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.artists)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_ALBUM)) + .setTitle(getString(R.string.albums)) + .setIconUri(Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.albums)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_SONG)) + .setTitle(getString(R.string.songs)) + .setIconUri(Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.songs)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + + mMediaRoot.add(new MediaBrowser.MediaItem( + new MediaDescription.Builder() + .setMediaId(Integer.toString(TYPE_PLAYLIST)) + .setTitle(getString(R.string.playlists)) + .setIconUri(Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2")) + .setSubtitle(getString(R.string.playlists)) + .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE + )); + + } + + private void loadChildren(final String parentId, final Result> result) { + + final List mediaItems = new ArrayList<>(); + + new AsyncTask() { + @Override + protected Void doInBackground(final Void... unused) { + + if (parentId.equals(MEDIA_ID_ROOT)) { + addMediaRoots(mediaItems); + } else { + switch (Integer.parseInt(Character.toString(parentId.charAt(0)))) { + /*case TYPE_ARTIST: + List artistList = ArtistLoader.getAllArtists(mContext); + for (Artist artist : artistList) { + String albumNmber = TimberUtils.makeLabel(mContext, R.plurals.Nalbums, artist.albumCount); + String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, artist.songCount); + fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_SONG_ALBUMS) + Long.toString(artist.id), artist.name, Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2"), TimberUtils.makeCombinedString(mContext, albumNmber, songCount), MediaBrowser.MediaItem.FLAG_BROWSABLE); + } + break; + case TYPE_ALBUM: + List albumList = AlbumLoader.getAllAlbums(mContext); + for (Album album : albumList) { + fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), album.artistName, MediaBrowser.MediaItem.FLAG_BROWSABLE); + } + break;*/ + case TYPE_SONG: + List songList = SongLoader.getAllSongs(mContext).blockingFirst(); + for (Song song : songList) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, RetroUtil.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } + break; + /* case TYPE_ALBUM_SONGS: + List albumSongList = AlbumSongLoader.getSongsForAlbum(mContext, Long.parseLong(parentId.substring(1))); + for (Song song : albumSongList) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.artistName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } + break; + case TYPE_ARTIST_SONG_ALBUMS: + fillMediaItems(mediaItems, Integer.toString(TYPE_ARTIST_ALL_SONGS) + Long.parseLong(parentId.substring(1)), "All songs", Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2"), "All songs by artist", MediaBrowser.MediaItem.FLAG_BROWSABLE); + List artistAlbums = ArtistAlbumLoader.getAlbumsForArtist(mContext, Long.parseLong(parentId.substring(1))); + for (Album album : artistAlbums) { + String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, album.songCount); + fillMediaItems(mediaItems, Integer.toString(TYPE_ALBUM_SONGS) + Long.toString(album.id), album.title, TimberUtils.getAlbumArtUri(album.id), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE); + + } + break; + case TYPE_ARTIST_ALL_SONGS: + List artistSongs = ArtistSongLoader.getSongsForArtist(mContext, Long.parseLong(parentId.substring(1))); + for (Song song : artistSongs) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } + break; + case TYPE_PLAYLIST: + List playlistList = PlaylistLoader.getPlaylists(mContext, false); + for (Playlist playlist : playlistList) { + String songCount = TimberUtils.makeLabel(mContext, R.plurals.Nsongs, playlist.songCount); + fillMediaItems(mediaItems, Integer.toString(TYPE_PLAYLIST_ALL_SONGS) + Long.toString(playlist.id), playlist.name, + Uri.parse("android.resource://" + + "naman14.timber/drawable/ic_empty_music2"), songCount, MediaBrowser.MediaItem.FLAG_BROWSABLE); + } + break; + case TYPE_PLAYLIST_ALL_SONGS: + List playlistSongs = PlaylistSongLoader.getSongsInPlaylist(mContext, Long.parseLong(parentId.substring(1))); + for (Song song : playlistSongs) { + fillMediaItems(mediaItems, String.valueOf(song.id), song.title, TimberUtils.getAlbumArtUri(song.albumId), song.albumName, MediaBrowser.MediaItem.FLAG_PLAYABLE); + } + break;*/ + + } + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + result.sendResult(mediaItems); + } + }.execute(); + + } + + private final class MediaSessionCallback extends MediaSession.Callback { + + @Override + public void onPlay() { + setSessionActive(); + } + + @Override + public void onSeekTo(long position) { + + } + + @Override + public void onPlayFromMediaId(final String mediaId, Bundle extras) { + long songId = Long.parseLong(mediaId); + setSessionActive(); + ArrayList songs = new ArrayList<>(); + songs.add(SongLoader.getSong(mContext, Integer.parseInt(mediaId)).blockingFirst()); + MusicPlayerRemote.openQueue(songs, 0, true); + } + + @Override + public void onPause() { + + } + + @Override + public void onStop() { + setSessionInactive(); + } + + @Override + public void onSkipToNext() { + + } + + @Override + public void onSkipToPrevious() { + + } + + @Override + public void onFastForward() { + + } + + @Override + public void onRewind() { + + } + + @Override + public void onCustomAction(String action, Bundle extras) { + + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/service/daydream/RetroMusicAlbums.java b/app/src/main/java/code/name/monkey/retromusic/service/daydream/RetroMusicAlbums.java new file mode 100644 index 00000000..c42dae27 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/daydream/RetroMusicAlbums.java @@ -0,0 +1,186 @@ +package code.name.monkey.retromusic.service.daydream; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.service.dreams.DreamService; +import android.support.transition.TransitionManager; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import io.reactivex.Observable; +import io.reactivex.ObservableSource; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.functions.Function; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by hemanths on 25/09/17. + */ + +public class RetroMusicAlbums extends DreamService { + @BindView(R.id.recycler_view) + RecyclerView mRecyclerView; + @BindView(R.id.title) + TextView mTitle; + @BindView(R.id.text) + TextView mText; + @BindView(R.id.title_container) + ViewGroup mViewGroup; + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) { + return; + } + String artist = intent.getStringExtra("artist"); + String track = intent.getStringExtra("track"); + if (mViewGroup != null) { + mViewGroup.setVisibility(View.VISIBLE); + TransitionManager.beginDelayedTransition(mViewGroup); + if (mTitle != null) { + mTitle.setText(track); + } + if (mText != null) { + mText.setText(artist); + } + } + + } + }; + private Unbinder unbinder; + private CompositeDisposable mDisposable; + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + View view = LayoutInflater.from(this).inflate(R.layout.dream_service, null); + setContentView(view); + unbinder = ButterKnife.bind(this, view); + + mRecyclerView.setItemAnimator(new DefaultItemAnimator()); + GridLayoutManager layoutManager = new GridLayoutManager(this, 4, LinearLayoutManager.VERTICAL, false); + mRecyclerView.setLayoutManager(layoutManager); + + + mDisposable.add(getPlayingQueue() + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .flatMap((Function, ObservableSource>>) songs -> Observable.create(e -> { + if (songs.isEmpty()) { + e.onNext(SongLoader.getAllSongs(RetroMusicAlbums.this).blockingFirst()); + } else { + e.onNext(songs); + } + e.onComplete(); + })) + .subscribe(songs -> { + if (songs.size() > 0) { + ImagesAdapter imagesAdapter = new ImagesAdapter(songs); + mRecyclerView.setAdapter(imagesAdapter); + } + })); + + } + + @Override + public void onCreate() { + super.onCreate(); + setInteractive(true); + setFullscreen(true); + + mDisposable = new CompositeDisposable(); + + IntentFilter iF = new IntentFilter(); + iF.addAction("com.android.music.musicservicecommand"); + iF.addAction("com.android.music.metachanged"); + registerReceiver(mBroadcastReceiver, iF); + + } + + @Override + public void onDestroy() { + super.onDestroy(); + unbinder.unbind(); + mDisposable.clear(); + unregisterReceiver(mBroadcastReceiver); + } + + private Observable> getPlayingQueue() { + return Observable.just(MusicPlayerRemote.getPlayingQueue()); + } + + class ImagesAdapter extends RecyclerView.Adapter { + + private final ArrayList list; + + public ImagesAdapter(ArrayList songs) { + this.list = songs; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + return new ViewHolder(LayoutInflater.from(getApplicationContext()) + .inflate(R.layout.image, viewGroup, false)); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int i) { + Song song = list.get(i); + SongGlideRequest.Builder.from(Glide.with(getApplicationContext()), song) + .checkIgnoreMediaStore(getApplicationContext()) + .generatePalette(getApplicationContext()).build() + .override(400, 400) + .into(new RetroMusicColoredTarget(holder.image) { + + @Override + public void onColorReady(int color) { + + } + }); + + } + + @Override + public int getItemCount() { + return list.size(); + } + + class ViewHolder extends MediaEntryViewHolder { + + public ViewHolder(View itemView) { + super(itemView); + } + + @Override + public void onClick(View v) { + super.onClick(v); + MusicPlayerRemote.openQueue(list, getAdapterPosition(), true); + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotification.java b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotification.java new file mode 100644 index 00000000..b444af22 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotification.java @@ -0,0 +1,80 @@ +package code.name.monkey.retromusic.service.notification; + + +import static android.content.Context.NOTIFICATION_SERVICE; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.os.Build; +import android.support.annotation.RequiresApi; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.service.MusicService; + + +public abstract class PlayingNotification { + + static final String NOTIFICATION_CHANNEL_ID = "playing_notification"; + private static final int NOTIFICATION_ID = 1; + private static final int NOTIFY_MODE_FOREGROUND = 1; + private static final int NOTIFY_MODE_BACKGROUND = 0; + protected MusicService service; + boolean stopped; + private int notifyMode = NOTIFY_MODE_BACKGROUND; + private NotificationManager notificationManager; + + public synchronized void init(MusicService service) { + this.service = service; + notificationManager = (NotificationManager) service.getSystemService(NOTIFICATION_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel(); + } + } + + public abstract void update(); + + public synchronized void stop() { + stopped = true; + service.stopForeground(true); + notificationManager.cancel(NOTIFICATION_ID); + } + + void updateNotifyModeAndPostNotification(Notification notification) { + int newNotifyMode; + if (service.isPlaying()) { + newNotifyMode = NOTIFY_MODE_FOREGROUND; + } else { + newNotifyMode = NOTIFY_MODE_BACKGROUND; + } + + if (notifyMode != newNotifyMode && newNotifyMode == NOTIFY_MODE_BACKGROUND) { + service.stopForeground(false); + } + + if (newNotifyMode == NOTIFY_MODE_FOREGROUND) { + service.startForeground(NOTIFICATION_ID, notification); + } else if (newNotifyMode == NOTIFY_MODE_BACKGROUND) { + notificationManager.notify(NOTIFICATION_ID, notification); + } + + notifyMode = newNotifyMode; + } + + @RequiresApi(26) + private void createNotificationChannel() { + NotificationChannel notificationChannel = notificationManager + .getNotificationChannel(NOTIFICATION_CHANNEL_ID); + if (notificationChannel == null) { + notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, + service.getString(R.string.playing_notification_name), + NotificationManager.IMPORTANCE_LOW); + notificationChannel + .setDescription(service.getString(R.string.playing_notification_description)); + notificationChannel.enableLights(false); + notificationChannel.enableVibration(false); + notificationChannel.setShowBadge(false); + + notificationManager.createNotificationChannel(notificationChannel); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.java b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.java new file mode 100644 index 00000000..cd424700 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.java @@ -0,0 +1,225 @@ +package code.name.monkey.retromusic.service.notification; + + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.text.TextUtils; +import android.view.View; +import android.widget.RemoteViews; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; + +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroColorUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +import static code.name.monkey.retromusic.Constants.ACTION_QUIT; +import static code.name.monkey.retromusic.Constants.ACTION_REWIND; +import static code.name.monkey.retromusic.Constants.ACTION_SKIP; +import static code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE; +import static code.name.monkey.retromusic.util.RetroUtil.createBitmap; + +public class PlayingNotificationImpl extends PlayingNotification { + + private Target target; + + + @Override + public synchronized void update() { + stopped = false; + + final Song song = service.getCurrentSong(); + + final boolean isPlaying = service.isPlaying(); + + final RemoteViews notificationLayout = new RemoteViews(service.getPackageName(), + R.layout.notification); + final RemoteViews notificationLayoutBig = new RemoteViews(service.getPackageName(), + R.layout.notification_big); + + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) { + notificationLayout.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + notificationLayout.setViewVisibility(R.id.media_titles, View.VISIBLE); + notificationLayout.setTextViewText(R.id.title, song.title); + notificationLayout.setTextViewText(R.id.text, song.artistName); + } + + if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName) && TextUtils + .isEmpty(song.albumName)) { + notificationLayoutBig.setViewVisibility(R.id.media_titles, View.INVISIBLE); + } else { + notificationLayoutBig.setViewVisibility(R.id.media_titles, View.VISIBLE); + notificationLayoutBig.setTextViewText(R.id.title, song.title); + notificationLayoutBig.setTextViewText(R.id.text, song.artistName); + notificationLayoutBig.setTextViewText(R.id.text2, song.albumName); + } + + linkButtons(notificationLayout, notificationLayoutBig); + + Intent action = new Intent(service, MainActivity.class); + action.putExtra("expand", true); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + + final PendingIntent clickIntent = PendingIntent + .getActivity(service, 0, action, PendingIntent.FLAG_UPDATE_CURRENT); + final PendingIntent deleteIntent = buildPendingIntent(service, ACTION_QUIT, null); + + final Notification notification = new NotificationCompat.Builder(service, + NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setContentIntent(clickIntent) + .setDeleteIntent(deleteIntent) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContent(notificationLayout) + .setCustomBigContentView(notificationLayoutBig) + .setOngoing(isPlaying) + .build(); + + final int bigNotificationImageSize = service.getResources() + .getDimensionPixelSize(R.dimen.notification_big_image_size); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(service), song) + .checkIgnoreMediaStore(service) + .generatePalette(service).build() + .into(new SimpleTarget(bigNotificationImageSize, + bigNotificationImageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + update(resource.getBitmap(), + PreferenceUtil.getInstance(service).isDominantColor() ? + RetroColorUtil.getDominantColor(resource.getBitmap(), Color.TRANSPARENT) : + RetroColorUtil.getColor(resource.getPalette(), Color.TRANSPARENT)); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null, Color.WHITE); + } + + private void update(@Nullable Bitmap bitmap, int bgColor) { + if (bitmap != null) { + notificationLayout.setImageViewBitmap(R.id.image, bitmap); + notificationLayoutBig.setImageViewBitmap(R.id.image, bitmap); + } else { + notificationLayout.setImageViewResource(R.id.image, R.drawable.default_album_art); + notificationLayoutBig + .setImageViewResource(R.id.image, R.drawable.default_album_art); + } + + if (!PreferenceUtil.getInstance(service).coloredNotification()) { + bgColor = Color.WHITE; + } + setBackgroundColor(bgColor); + setNotificationContent(ColorUtil.isColorLight(bgColor)); + + if (stopped) { + return; // notification has been stopped before loading was finished + } + updateNotifyModeAndPostNotification(notification); + } + + private void setBackgroundColor(int color) { + notificationLayout.setInt(R.id.root, "setBackgroundColor", color); + notificationLayoutBig.setInt(R.id.root, "setBackgroundColor", color); + } + + private void setNotificationContent(boolean dark) { + int primary = MaterialValueHelper.getPrimaryTextColor(service, dark); + int secondary = MaterialValueHelper.getSecondaryTextColor(service, dark); + + Bitmap close = createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_close_white_24dp, primary), + 1.5f); + Bitmap prev = createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, + primary), 1.5f); + Bitmap next = createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, + primary), 1.5f); + Bitmap playPause = createBitmap(RetroUtil.getTintedVectorDrawable(service, + isPlaying ? R.drawable.ic_pause_white_24dp + : R.drawable.ic_play_arrow_white_24dp, primary), 1.5f); + + notificationLayout.setTextColor(R.id.title, primary); + notificationLayout.setTextColor(R.id.text, secondary); + notificationLayout.setImageViewBitmap(R.id.action_prev, prev); + notificationLayout.setImageViewBitmap(R.id.action_next, next); + notificationLayout.setImageViewBitmap(R.id.action_play_pause, playPause); + + notificationLayoutBig.setTextColor(R.id.title, primary); + notificationLayoutBig.setTextColor(R.id.text, secondary); + notificationLayoutBig.setTextColor(R.id.text2, secondary); + + notificationLayoutBig.setImageViewBitmap(R.id.action_quit, close); + notificationLayoutBig.setImageViewBitmap(R.id.action_prev, prev); + notificationLayoutBig.setImageViewBitmap(R.id.action_next, next); + notificationLayoutBig.setImageViewBitmap(R.id.action_play_pause, playPause); + + } + }); + } + }); + } + + private void linkButtons(final RemoteViews notificationLayout, + final RemoteViews notificationLayoutBig) { + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(service, MusicService.class); + + // Previous track + pendingIntent = buildPendingIntent(service, ACTION_REWIND, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_prev, pendingIntent); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(service, ACTION_TOGGLE_PAUSE, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(service, ACTION_SKIP, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_next, pendingIntent); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_next, pendingIntent); + + // Close + pendingIntent = buildPendingIntent(service, ACTION_QUIT, serviceName); + notificationLayoutBig.setOnClickPendingIntent(R.id.action_quit, pendingIntent); + } + + private PendingIntent buildPendingIntent(Context context, final String action, + final ComponentName serviceName) { + Intent intent = new Intent(action); + intent.setComponent(serviceName); + return PendingIntent.getService(context, 0, intent, 0); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl24.java b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl24.java new file mode 100644 index 00000000..c0360102 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl24.java @@ -0,0 +1,146 @@ +package code.name.monkey.retromusic.service.notification; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.app.NotificationCompat; +import android.support.v4.media.app.NotificationCompat.MediaStyle; +import android.text.Html; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroColorUtil; + +import static code.name.monkey.retromusic.Constants.ACTION_QUIT; +import static code.name.monkey.retromusic.Constants.ACTION_REWIND; +import static code.name.monkey.retromusic.Constants.ACTION_SKIP; +import static code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE; + +public class PlayingNotificationImpl24 extends PlayingNotification { + + @Override + public synchronized void update() { + stopped = false; + + final Song song = service.getCurrentSong(); + final boolean isPlaying = service.isPlaying(); + + + final int playButtonResId = isPlaying ? R.drawable.ic_pause_white_24dp : + R.drawable.ic_play_arrow_white_24dp; + + Intent action = new Intent(service, MainActivity.class); + action.putExtra("expand", true); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + final PendingIntent clickIntent = PendingIntent + .getActivity(service, 0, action, PendingIntent.FLAG_UPDATE_CURRENT); + + final ComponentName serviceName = new ComponentName(service, MusicService.class); + Intent intent = new Intent(Constants.ACTION_QUIT); + intent.setComponent(serviceName); + final PendingIntent deleteIntent = PendingIntent.getService(service, 0, intent, 0); + + final int bigNotificationImageSize = service.getResources() + .getDimensionPixelSize(R.dimen.notification_big_image_size); + service.runOnUiThread(() -> SongGlideRequest.Builder.from(Glide.with(service), song) + .checkIgnoreMediaStore(service) + .generatePalette(service).build() + .into(new SimpleTarget(bigNotificationImageSize, + bigNotificationImageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + update(resource.getBitmap(), + PreferenceUtil.getInstance(RetroApplication.getInstance()).isDominantColor() ? + RetroColorUtil.getDominantColor(resource.getBitmap(), Color.TRANSPARENT) : + RetroColorUtil.getColor(resource.getPalette(), Color.TRANSPARENT)); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + update(null, Color.TRANSPARENT); + } + + void update(Bitmap bitmap, int color) { + if (bitmap == null) { + bitmap = BitmapFactory + .decodeResource(service.getResources(), R.drawable.default_album_art); + } + NotificationCompat.Action playPauseAction = new NotificationCompat.Action( + playButtonResId, + service.getString(R.string.action_play_pause), + retrievePlaybackAction(ACTION_TOGGLE_PAUSE)); + + NotificationCompat.Action closeAction = new NotificationCompat.Action( + R.drawable.ic_close_white_24dp, + service.getString(R.string.close_notification), + retrievePlaybackAction(ACTION_QUIT)); + + NotificationCompat.Action previousAction = new NotificationCompat.Action( + R.drawable.ic_skip_previous_white_24dp, + service.getString(R.string.action_previous), + retrievePlaybackAction(ACTION_REWIND)); + + NotificationCompat.Action nextAction = new NotificationCompat.Action( + R.drawable.ic_skip_next_white_24dp, + service.getString(R.string.action_next), + retrievePlaybackAction(ACTION_SKIP)); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(service, + NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(bitmap) + .setContentIntent(clickIntent) + .setDeleteIntent(deleteIntent) + .setContentTitle(Html.fromHtml("" + song.title + "")) + .setContentText(song.artistName) + .setSubText(Html.fromHtml("" + song.albumName + "")) + .setOngoing(isPlaying) + .setShowWhen(false) + .addAction(previousAction) + .addAction(playPauseAction) + .addAction(nextAction) + .addAction(closeAction); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setStyle(new MediaStyle() + .setMediaSession(service.getMediaSession().getSessionToken()) + .setShowActionsInCompactView(0, 1, 2, 3, 4)) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O && PreferenceUtil + .getInstance(service).coloredNotification()) { + builder.setColor(color); + } + } + + if (stopped) { + return; // notification has been stopped before loading was finished + } + updateNotifyModeAndPostNotification(builder.build()); + } + })); + } + + private PendingIntent retrievePlaybackAction(final String action) { + final ComponentName serviceName = new ComponentName(service, MusicService.class); + Intent intent = new Intent(action); + intent.setComponent(serviceName); + return PendingIntent.getService(service, 0, intent, 0); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.java b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.java new file mode 100644 index 00000000..6a305c6c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.java @@ -0,0 +1,244 @@ +package code.name.monkey.retromusic.service.notification; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.widget.RemoteViews; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; + +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.util.color.MediaNotificationProcessor; + +import static code.name.monkey.retromusic.Constants.ACTION_QUIT; +import static code.name.monkey.retromusic.Constants.ACTION_REWIND; +import static code.name.monkey.retromusic.Constants.ACTION_SKIP; +import static code.name.monkey.retromusic.Constants.ACTION_TOGGLE_PAUSE; +import static code.name.monkey.retromusic.util.RetroUtil.createBitmap; + +/** + * @author Hemanth S (h4h13). + */ +public class PlayingNotificationOreo extends PlayingNotification { + + private Target target; + + private RemoteViews getCombinedRemoteViews(boolean collapsed, Song song) { + RemoteViews remoteViews = new RemoteViews(service.getPackageName(), + collapsed ? R.layout.layout_notification_collapsed : R.layout.layout_notification_expanded); + + remoteViews.setTextViewText(R.id.appName, service.getString(R.string.app_name) + " • " + song.albumName); + remoteViews.setTextViewText(R.id.title, song.title); + remoteViews.setTextViewText(R.id.subtitle, song.artistName); + + TypedArray typedArray = service + .obtainStyledAttributes(new int[]{android.R.attr.selectableItemBackground}); + int selectableItemBackground = typedArray.getResourceId(0, 0); + typedArray.recycle(); + + remoteViews.setInt(R.id.content, "setBackgroundResource", selectableItemBackground); + + linkButtons(remoteViews); + + //setNotificationContent(remoteViews, ColorUtil.isColorLight(backgroundColor)); + return remoteViews; + } + + @Override + public void update() { + stopped = false; + final Song song = service.getCurrentSong(); + final boolean isPlaying = service.isPlaying(); + + final RemoteViews notificationLayout = getCombinedRemoteViews(true, song); + final RemoteViews notificationLayoutBig = getCombinedRemoteViews(false, song); + + Intent action = new Intent(service, MainActivity.class); + action.putExtra("expand", true); + action.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + + final PendingIntent clickIntent = PendingIntent + .getActivity(service, 0, action, PendingIntent.FLAG_UPDATE_CURRENT); + final PendingIntent deleteIntent = buildPendingIntent(service, ACTION_QUIT, null); + + final NotificationCompat.Builder builder = new NotificationCompat.Builder(service, + NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setContentIntent(clickIntent) + .setDeleteIntent(deleteIntent) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setCustomContentView(notificationLayout) + .setCustomBigContentView(notificationLayoutBig) + .setOngoing(isPlaying); + + final int bigNotificationImageSize = service.getResources() + .getDimensionPixelSize(R.dimen.notification_big_image_size); + service.runOnUiThread(new Runnable() { + @Override + public void run() { + if (target != null) { + Glide.clear(target); + } + target = SongGlideRequest.Builder.from(Glide.with(service), song) + .checkIgnoreMediaStore(service) + .generatePalette(service).build() + .into(new SimpleTarget(bigNotificationImageSize, + bigNotificationImageSize) { + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + + MediaNotificationProcessor mediaNotificationProcessor = new MediaNotificationProcessor(service, service, new MediaNotificationProcessor.onColorThing() { + @Override + public void bothColor(int i, int i2) { + update(resource.getBitmap(), i, i2); + } + }); + mediaNotificationProcessor.processNotification(resource.getBitmap()); + + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + update(null, Color.WHITE, Color.BLACK); + } + + private void update(@Nullable Bitmap bitmap, int bgColor, int textColor) { + if (bitmap != null) { + notificationLayout.setImageViewBitmap(R.id.largeIcon, bitmap); + notificationLayoutBig.setImageViewBitmap(R.id.largeIcon, bitmap); + } else { + notificationLayout + .setImageViewResource(R.id.largeIcon, R.drawable.default_album_art); + notificationLayoutBig + .setImageViewResource(R.id.largeIcon, R.drawable.default_album_art); + } + + if (!PreferenceUtil.getInstance(service).coloredNotification()) { + bgColor = Color.WHITE; + } + setBackgroundColor(bgColor); + setNotificationContent(ColorUtil.isColorLight(bgColor)); + + if (stopped) { + return; // notification has been stopped before loading was finished + } + updateNotifyModeAndPostNotification(builder.build()); + } + + private void setBackgroundColor(int color) { + + notificationLayout.setInt(R.id.image, "setBackgroundColor", color); + notificationLayoutBig.setInt(R.id.image, "setBackgroundColor", color); + + notificationLayout.setInt(R.id.foregroundImage, "setColorFilter", color); + notificationLayoutBig.setInt(R.id.foregroundImage, "setColorFilter", color); + } + + private void setNotificationContent(boolean dark) { + int primary = MaterialValueHelper.getPrimaryTextColor(service, dark); + int secondary = MaterialValueHelper.getSecondaryTextColor(service, dark); + + Bitmap close = createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_close_white_24dp, primary), + 1.3f); + Bitmap prev = createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_previous_white_24dp, + primary), 1.3f); + Bitmap next = createBitmap( + RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_skip_next_white_24dp, + primary), 1.3f); + Bitmap playPause = createBitmap(RetroUtil.getTintedVectorDrawable(service, + isPlaying ? R.drawable.ic_pause_white_24dp + : R.drawable.ic_play_arrow_white_24dp, primary), 1.3f); + + notificationLayout.setTextColor(R.id.title, primary); + notificationLayout.setTextColor(R.id.subtitle, secondary); + notificationLayout.setTextColor(R.id.appName, secondary); + + notificationLayout.setImageViewBitmap(R.id.action_prev, prev); + notificationLayout.setImageViewBitmap(R.id.action_next, next); + notificationLayout.setImageViewBitmap(R.id.action_play_pause, playPause); + + notificationLayoutBig.setTextColor(R.id.title, primary); + notificationLayoutBig.setTextColor(R.id.subtitle, secondary); + notificationLayoutBig.setTextColor(R.id.appName, secondary); + + notificationLayoutBig.setImageViewBitmap(R.id.action_quit, close); + notificationLayoutBig.setImageViewBitmap(R.id.action_prev, prev); + notificationLayoutBig.setImageViewBitmap(R.id.action_next, next); + notificationLayoutBig.setImageViewBitmap(R.id.action_play_pause, playPause); + + notificationLayout.setImageViewBitmap(R.id.smallIcon, + createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_notification, secondary), 0.6f)); + notificationLayoutBig.setImageViewBitmap(R.id.smallIcon, + createBitmap(RetroUtil.getTintedVectorDrawable(service, R.drawable.ic_notification, secondary), 0.6f)); + + notificationLayout.setInt(R.id.arrow, "setColorFilter", secondary); + notificationLayoutBig.setInt(R.id.arrow, "setColorFilter", secondary); + + } + }); + } + }); + + if (stopped) { + return; // notification has been stopped before loading was finished + } + updateNotifyModeAndPostNotification(builder.build()); + } + + + private PendingIntent buildPendingIntent(Context context, final String action, + final ComponentName serviceName) { + Intent intent = new Intent(action); + intent.setComponent(serviceName); + return PendingIntent.getService(context, 0, intent, 0); + } + + + private void linkButtons(final RemoteViews notificationLayout) { + PendingIntent pendingIntent; + + final ComponentName serviceName = new ComponentName(service, MusicService.class); + + // Previous track + pendingIntent = buildPendingIntent(service, ACTION_REWIND, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_prev, pendingIntent); + + // Play and pause + pendingIntent = buildPendingIntent(service, ACTION_TOGGLE_PAUSE, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_play_pause, pendingIntent); + + // Next track + pendingIntent = buildPendingIntent(service, ACTION_SKIP, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_next, pendingIntent); + + // Close + pendingIntent = buildPendingIntent(service, ACTION_QUIT, serviceName); + notificationLayout.setOnClickPendingIntent(R.id.action_quit, pendingIntent); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/service/playback/Playback.java b/app/src/main/java/code/name/monkey/retromusic/service/playback/Playback.java new file mode 100644 index 00000000..8863b36e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/service/playback/Playback.java @@ -0,0 +1,43 @@ +package code.name.monkey.retromusic.service.playback; + +import android.support.annotation.Nullable; + + +public interface Playback { + + boolean setDataSource(String path); + + void setNextDataSource(@Nullable String path); + + void setCallbacks(PlaybackCallbacks callbacks); + + boolean isInitialized(); + + boolean start(); + + void stop(); + + void release(); + + boolean pause(); + + boolean isPlaying(); + + int duration(); + + int position(); + + int seek(int whereto); + + boolean setVolume(float vol); + + boolean setAudioSessionId(int sessionId); + + int getAudioSessionId(); + + interface PlaybackCallbacks { + void onTrackWentToNext(); + + void onTrackEnded(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/swipebtn/DimentionUtils.java b/app/src/main/java/code/name/monkey/retromusic/swipebtn/DimentionUtils.java new file mode 100755 index 00000000..587055a5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/swipebtn/DimentionUtils.java @@ -0,0 +1,13 @@ +package code.name.monkey.retromusic.swipebtn; + +import android.content.Context; + +final class DimentionUtils { + + private DimentionUtils() { + } + + static float convertPixelsToSp(float px, Context context) { + return px / context.getResources().getDisplayMetrics().scaledDensity; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/swipebtn/OnActiveListener.java b/app/src/main/java/code/name/monkey/retromusic/swipebtn/OnActiveListener.java new file mode 100755 index 00000000..429584ce --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/swipebtn/OnActiveListener.java @@ -0,0 +1,5 @@ +package code.name.monkey.retromusic.swipebtn; + +public interface OnActiveListener { + void onActive(); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/swipebtn/OnStateChangeListener.java b/app/src/main/java/code/name/monkey/retromusic/swipebtn/OnStateChangeListener.java new file mode 100755 index 00000000..c4adf4a2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/swipebtn/OnStateChangeListener.java @@ -0,0 +1,5 @@ +package code.name.monkey.retromusic.swipebtn; + +public interface OnStateChangeListener { + void onStateChange(boolean active); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/swipebtn/SwipeButton.java b/app/src/main/java/code/name/monkey/retromusic/swipebtn/SwipeButton.java new file mode 100755 index 00000000..eaecc952 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/swipebtn/SwipeButton.java @@ -0,0 +1,486 @@ +package code.name.monkey.retromusic.swipebtn; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import code.name.monkey.retromusic.R; + +public class SwipeButton extends RelativeLayout { + + + private static final int ENABLED = 0; + private static final int DISABLED = 1; + private ImageView swipeButtonInner; + private float initialX; + private boolean active; + private TextView centerText; + private ViewGroup background; + private Drawable disabledDrawable; + private Drawable enabledDrawable; + private OnStateChangeListener onStateChangeListener; + private OnActiveListener onActiveListener; + private int collapsedWidth; + private int collapsedHeight; + + private LinearLayout layer; + private boolean trailEnabled = false; + private boolean hasActivationState; + + public SwipeButton(Context context) { + super(context); + + init(context, null, -1, -1); + } + + public SwipeButton(Context context, AttributeSet attrs) { + super(context, attrs); + + init(context, attrs, -1, -1); + } + + public SwipeButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + init(context, attrs, defStyleAttr, -1); + } + + @TargetApi(21) + public SwipeButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + init(context, attrs, defStyleAttr, defStyleRes); + } + + public void setCenterTextColor(int color) { + if (centerText == null) { + return; + } + centerText.setTextColor(color); + } + + public boolean isActive() { + return active; + } + + public void setText(String text) { + centerText.setText(text); + } + + public void setBackground(Drawable drawable) { + background.setBackground(drawable); + } + + public ViewGroup getSwipeBackground() { + return background; + } + + public void setSlidingButtonBackground(Drawable drawable) { + background.setBackground(drawable); + } + + public void setDisabledDrawable(Drawable drawable) { + disabledDrawable = drawable; + + if (!active) { + swipeButtonInner.setImageDrawable(drawable); + } + } + + public void setButtonBackground(Drawable buttonBackground) { + if (buttonBackground != null) { + swipeButtonInner.setBackground(buttonBackground); + } + } + + public void setEnabledDrawable(Drawable drawable) { + enabledDrawable = drawable; + + if (active) { + swipeButtonInner.setImageDrawable(drawable); + } + } + + public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) { + this.onStateChangeListener = onStateChangeListener; + } + + public void setOnActiveListener(OnActiveListener onActiveListener) { + this.onActiveListener = onActiveListener; + } + + public void setInnerTextPadding(int left, int top, int right, int bottom) { + centerText.setPadding(left, top, right, bottom); + } + + public void setSwipeButtonPadding(int left, int top, int right, int bottom) { + swipeButtonInner.setPadding(left, top, right, bottom); + } + + public void setHasActivationState(boolean hasActivationState) { + this.hasActivationState = hasActivationState; + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + hasActivationState = true; + + background = new RelativeLayout(context); + + LayoutParams layoutParamsView = new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + layoutParamsView.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); + + addView(background, layoutParamsView); + + final TextView centerText = new TextView(context); + this.centerText = centerText; + centerText.setGravity(Gravity.CENTER); + + LayoutParams layoutParams = new LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); + + background.addView(centerText, layoutParams); + + final ImageView swipeButton = new ImageView(context); + this.swipeButtonInner = swipeButton; + + if (attrs != null && defStyleAttr == -1 && defStyleRes == -1) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwipeButton, + defStyleAttr, defStyleRes); + + collapsedWidth = (int) typedArray.getDimension(R.styleable.SwipeButton_button_image_width, + ViewGroup.LayoutParams.WRAP_CONTENT); + collapsedHeight = (int) typedArray.getDimension(R.styleable.SwipeButton_button_image_height, + ViewGroup.LayoutParams.WRAP_CONTENT); + trailEnabled = typedArray.getBoolean(R.styleable.SwipeButton_button_trail_enabled, + false); + Drawable trailingDrawable = typedArray.getDrawable(R.styleable.SwipeButton_button_trail_drawable); + + Drawable backgroundDrawable = typedArray.getDrawable(R.styleable.SwipeButton_inner_text_background); + + if (backgroundDrawable != null) { + background.setBackground(backgroundDrawable); + } else { + background.setBackground(ContextCompat.getDrawable(context, R.drawable.shape_rounded_edit)); + } + + if (trailEnabled) { + layer = new LinearLayout(context); + + if (trailingDrawable != null) { + layer.setBackground(trailingDrawable); + } else { + layer.setBackground(typedArray.getDrawable(R.styleable.SwipeButton_button_background)); + } + + layer.setGravity(Gravity.START); + layer.setVisibility(View.GONE); + background.addView(layer, layoutParamsView); + } + + centerText.setText(typedArray.getText(R.styleable.SwipeButton_inner_text)); + centerText.setTextColor(typedArray.getColor(R.styleable.SwipeButton_inner_text_color, + Color.WHITE)); + + float textSize = DimentionUtils.convertPixelsToSp( + typedArray.getDimension(R.styleable.SwipeButton_inner_text_size, 0), context); + + if (textSize != 0) { + centerText.setTextSize(textSize); + } else { + centerText.setTextSize(12); + } + + disabledDrawable = typedArray.getDrawable(R.styleable.SwipeButton_button_image_disabled); + enabledDrawable = typedArray.getDrawable(R.styleable.SwipeButton_button_image_enabled); + float innerTextLeftPadding = typedArray.getDimension( + R.styleable.SwipeButton_inner_text_left_padding, 0); + float innerTextTopPadding = typedArray.getDimension( + R.styleable.SwipeButton_inner_text_top_padding, 0); + float innerTextRightPadding = typedArray.getDimension( + R.styleable.SwipeButton_inner_text_right_padding, 0); + float innerTextBottomPadding = typedArray.getDimension( + R.styleable.SwipeButton_inner_text_bottom_padding, 0); + + int initialState = typedArray.getInt(R.styleable.SwipeButton_initial_state, DISABLED); + + if (initialState == ENABLED) { + LayoutParams layoutParamsButton = new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + + layoutParamsButton.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); + layoutParamsButton.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + + swipeButton.setImageDrawable(enabledDrawable); + + addView(swipeButton, layoutParamsButton); + + active = true; + } else { + LayoutParams layoutParamsButton = new LayoutParams(collapsedWidth, collapsedHeight); + + layoutParamsButton.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); + layoutParamsButton.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + + swipeButton.setImageDrawable(disabledDrawable); + + addView(swipeButton, layoutParamsButton); + + active = false; + } + + centerText.setPadding((int) innerTextLeftPadding, + (int) innerTextTopPadding, + (int) innerTextRightPadding, + (int) innerTextBottomPadding); + + Drawable buttonBackground = typedArray.getDrawable(R.styleable.SwipeButton_button_background); + + if (buttonBackground != null) { + swipeButton.setBackground(buttonBackground); + } else { + swipeButton.setBackground(ContextCompat.getDrawable(context, R.drawable.shape_rounded_edit)); + } + + float buttonLeftPadding = typedArray.getDimension( + R.styleable.SwipeButton_button_left_padding, 0); + float buttonTopPadding = typedArray.getDimension( + R.styleable.SwipeButton_button_top_padding, 0); + float buttonRightPadding = typedArray.getDimension( + R.styleable.SwipeButton_button_right_padding, 0); + float buttonBottomPadding = typedArray.getDimension( + R.styleable.SwipeButton_button_bottom_padding, 0); + + swipeButton.setPadding((int) buttonLeftPadding, + (int) buttonTopPadding, + (int) buttonRightPadding, + (int) buttonBottomPadding); + + hasActivationState = typedArray.getBoolean(R.styleable.SwipeButton_has_activate_state, true); + + typedArray.recycle(); + } + + setOnTouchListener(getButtonTouchListener()); + } + + private OnTouchListener getButtonTouchListener() { + return new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + return !TouchUtils.isTouchOutsideInitialPosition(event, swipeButtonInner); + case MotionEvent.ACTION_MOVE: + if (initialX == 0) { + initialX = swipeButtonInner.getX(); + } + + if (event.getX() > swipeButtonInner.getWidth() / 2 && + event.getX() + swipeButtonInner.getWidth() / 2 < getWidth()) { + swipeButtonInner.setX(event.getX() - swipeButtonInner.getWidth() / 2); + centerText.setAlpha(1 - 1.3f * (swipeButtonInner.getX() + swipeButtonInner.getWidth()) / getWidth()); + setTrailingEffect(); + } + + if (event.getX() + swipeButtonInner.getWidth() / 2 > getWidth() && + swipeButtonInner.getX() + swipeButtonInner.getWidth() / 2 < getWidth()) { + swipeButtonInner.setX(getWidth() - swipeButtonInner.getWidth()); + } + + if (event.getX() < swipeButtonInner.getWidth() / 2) { + swipeButtonInner.setX(0); + } + + return true; + case MotionEvent.ACTION_UP: + if (active) { + collapseButton(); + } else { + if (swipeButtonInner.getX() + swipeButtonInner.getWidth() > getWidth() * 0.9) { + if (hasActivationState) { + expandButton(); + } else if (onActiveListener != null) { + onActiveListener.onActive(); + moveButtonBack(); + } + } else { + moveButtonBack(); + } + } + + return true; + } + + return false; + } + }; + } + + private void expandButton() { + final ValueAnimator positionAnimator = + ValueAnimator.ofFloat(swipeButtonInner.getX(), 0); + positionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float x = (Float) positionAnimator.getAnimatedValue(); + swipeButtonInner.setX(x); + } + }); + + + final ValueAnimator widthAnimator = ValueAnimator.ofInt( + swipeButtonInner.getWidth(), + getWidth()); + + widthAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + ViewGroup.LayoutParams params = swipeButtonInner.getLayoutParams(); + params.width = (Integer) widthAnimator.getAnimatedValue(); + swipeButtonInner.setLayoutParams(params); + } + }); + + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + + active = true; + swipeButtonInner.setImageDrawable(enabledDrawable); + + if (onStateChangeListener != null) { + onStateChangeListener.onStateChange(active); + } + + if (onActiveListener != null) { + onActiveListener.onActive(); + } + } + }); + + animatorSet.playTogether(positionAnimator, widthAnimator); + animatorSet.start(); + } + + private void moveButtonBack() { + final ValueAnimator positionAnimator = + ValueAnimator.ofFloat(swipeButtonInner.getX(), 0); + positionAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + positionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float x = (Float) positionAnimator.getAnimatedValue(); + swipeButtonInner.setX(x); + setTrailingEffect(); + } + }); + + positionAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (layer != null) { + layer.setVisibility(View.GONE); + } + } + }); + + ObjectAnimator objectAnimator = ObjectAnimator.ofFloat( + centerText, "alpha", 1); + + positionAnimator.setDuration(200); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(objectAnimator, positionAnimator); + animatorSet.start(); + } + + private void collapseButton() { + int finalWidth; + + if (collapsedWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { + finalWidth = swipeButtonInner.getHeight(); + } else { + finalWidth = collapsedWidth; + } + + final ValueAnimator widthAnimator = ValueAnimator.ofInt(swipeButtonInner.getWidth(), finalWidth); + + widthAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + ViewGroup.LayoutParams params = swipeButtonInner.getLayoutParams(); + params.width = (Integer) widthAnimator.getAnimatedValue(); + swipeButtonInner.setLayoutParams(params); + setTrailingEffect(); + } + }); + + widthAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + active = false; + swipeButtonInner.setImageDrawable(disabledDrawable); + if (onStateChangeListener != null) { + onStateChangeListener.onStateChange(active); + } + if (layer != null) { + layer.setVisibility(View.GONE); + } + } + }); + + ObjectAnimator objectAnimator = ObjectAnimator.ofFloat( + centerText, "alpha", 1); + + AnimatorSet animatorSet = new AnimatorSet(); + + animatorSet.playTogether(objectAnimator, widthAnimator); + animatorSet.start(); + } + + private void setTrailingEffect() { + if (trailEnabled) { + layer.setVisibility(View.VISIBLE); + layer.setLayoutParams(new LayoutParams( + (int) (swipeButtonInner.getX() + swipeButtonInner.getWidth() / 3), centerText.getHeight())); + } + } + + public void toggleState() { + if (isActive()) { + collapseButton(); + } else { + expandButton(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/swipebtn/TouchUtils.java b/app/src/main/java/code/name/monkey/retromusic/swipebtn/TouchUtils.java new file mode 100755 index 00000000..b9762592 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/swipebtn/TouchUtils.java @@ -0,0 +1,13 @@ +package code.name.monkey.retromusic.swipebtn; + +import android.view.MotionEvent; +import android.view.View; + +final class TouchUtils { + private TouchUtils() { + } + + static boolean isTouchOutsideInitialPosition(MotionEvent event, View view) { + return event.getX() > view.getX() + view.getWidth(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/transform/CustPagerTransformer.java b/app/src/main/java/code/name/monkey/retromusic/transform/CustPagerTransformer.java new file mode 100644 index 00000000..6e72fed3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/transform/CustPagerTransformer.java @@ -0,0 +1,45 @@ +package code.name.monkey.retromusic.transform; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.view.View; + +/** + * 实现ViewPager左右滑动时的时差 + * Created by xmuSistone on 2016/9/18. + */ +public class CustPagerTransformer implements ViewPager.PageTransformer { + + private int maxTranslateOffsetX; + private ViewPager viewPager; + + public CustPagerTransformer(Context context) { + this.maxTranslateOffsetX = dp2px(context, 180); + } + + public void transformPage(View view, float position) { + if (viewPager == null) { + viewPager = (ViewPager) view.getParent(); + } + + int leftInScreen = view.getLeft() - viewPager.getScrollX(); + int centerXInViewPager = leftInScreen + view.getMeasuredWidth() / 2; + int offsetX = centerXInViewPager - viewPager.getMeasuredWidth() / 2; + float offsetRate = (float) offsetX * 0.20f / viewPager.getMeasuredWidth(); + float scaleFactor = 1 - Math.abs(offsetRate); + if (scaleFactor > 0) { + view.setScaleX(scaleFactor); + view.setScaleY(scaleFactor); + view.setTranslationX(-maxTranslateOffsetX * offsetRate); + } + } + + /** + * dp和像素转换 + */ + private int dp2px(Context context, float dipValue) { + float m = context.getResources().getDisplayMetrics().density; + return (int) (dipValue * m + 0.5f); + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/transform/NormalPageTransformer.java b/app/src/main/java/code/name/monkey/retromusic/transform/NormalPageTransformer.java new file mode 100644 index 00000000..4ad5c7eb --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/transform/NormalPageTransformer.java @@ -0,0 +1,47 @@ +package code.name.monkey.retromusic.transform; + +import android.support.v4.view.ViewPager; +import android.view.View; + +/** + * @author Hemanth S (h4h13). + */ + +public class NormalPageTransformer implements ViewPager.PageTransformer { + private static final float MIN_SCALE = 0.85f; + private static final float MIN_ALPHA = 0.5f; + + @Override + public void transformPage(View view, float position) { + int pageWidth = view.getWidth(); + int pageHeight = view.getHeight(); + + if (position < -1) { // [-Infinity,-1) + // This page is way off-screen to the left. + view.setAlpha(1); + view.setScaleY(0.7f); + } else if (position <= 1) { // [-1,1] + // Modify the default slide transition to shrink the page as well + float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position)); + float vertMargin = pageHeight * (1 - scaleFactor) / 2; + float horzMargin = pageWidth * (1 - scaleFactor) / 2; + if (position < 0) { + view.setTranslationX(horzMargin - vertMargin / 2); + } else { + view.setTranslationX(-horzMargin + vertMargin / 2); + } + + // Scale the page down (between MIN_SCALE and 1) + view.setScaleX(scaleFactor); + view.setScaleY(scaleFactor); + + // Fade the page relative to its size. + //view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE) / (1 - MIN_SCALE) * (1 - MIN_ALPHA)); + + } else { // (1,+Infinity] + // This page is way off-screen to the right. + view.setAlpha(1); + view.setScaleY(0.7f); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/transform/ParallaxPagerTransformer.java b/app/src/main/java/code/name/monkey/retromusic/transform/ParallaxPagerTransformer.java new file mode 100644 index 00000000..c2479db4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/transform/ParallaxPagerTransformer.java @@ -0,0 +1,56 @@ +package code.name.monkey.retromusic.transform; + +import android.annotation.TargetApi; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v4.view.ViewPager; +import android.util.Log; +import android.view.View; + +/** + * Created by xgc1986 on 2/Apr/2016 + */ + +public class ParallaxPagerTransformer implements ViewPager.PageTransformer { + private int id; + private int border = 0; + private float speed = 0.2f; + + public ParallaxPagerTransformer(int id) { + this.id = id; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @Override + public void transformPage(@NonNull View view, float position) { + + View parallaxView = view.findViewById(id); + + if (view == null ) { + Log.w("ParallaxPager", "There is no view"); + } + + if (parallaxView != null) { + if (position > -1 && position < 1) { + float width = parallaxView.getWidth(); + parallaxView.setTranslationX(-(position * width * speed)); + float sc = ((float)view.getWidth() - border)/ view.getWidth(); + if (position == 0) { + view.setScaleX(1); + view.setScaleY(1); + } else { + view.setScaleX(sc); + view.setScaleY(sc); + } + } + } + } + + public void setBorder(int px) { + border = px; + } + + public void setSpeed(float speed) { + this.speed = speed; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/AboutActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AboutActivity.java new file mode 100644 index 00000000..b6ae31cb --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AboutActivity.java @@ -0,0 +1,167 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.v4.app.ShareCompat; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.Constants; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; +import code.name.monkey.retromusic.util.NavigationUtil; + +import static code.name.monkey.retromusic.Constants.APP_INSTAGRAM_LINK; +import static code.name.monkey.retromusic.Constants.APP_TWITTER_LINK; +import static code.name.monkey.retromusic.Constants.GITHUB_PROJECT; +import static code.name.monkey.retromusic.Constants.GOOGLE_PLUS_COMMUNITY; +import static code.name.monkey.retromusic.Constants.RATE_ON_GOOGLE_PLAY; +import static code.name.monkey.retromusic.Constants.TELEGRAM_CHANGE_LOG; +import static code.name.monkey.retromusic.Constants.TRANSLATE; + +/** + * @author Hemanth S (h4h13) + */ + +public class AboutActivity extends AbsBaseActivity { + @BindView(R.id.toolbar) + Toolbar toolbar; + + @BindView(R.id.app_bar) + AppBarLayout appBar; + @BindView(R.id.buy_pro) + TextView supportText; + @BindView(R.id.app_version) + TextView appVersion; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_about); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + setUpToolbar(); + + appVersion.setText(getAppVersion()); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void setUpToolbar() { + int primaryColor = ThemeStore.primaryColor(this); + toolbar.setBackgroundColor(primaryColor); + appBar.setBackgroundColor(primaryColor); + setTitle(R.string.action_about); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + supportText.setText(RetroApplication.isProVersion() ? R.string.thank_you : R.string.buy_retromusic_pro); + } + + + private void openUrl(String url) { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } + + + @OnClick({R.id.app_github, R.id.faq_link, + R.id.app_google_plus, R.id.app_translation, + R.id.support_container, R.id.app_rate, R.id.app_share, R.id.pro_container, + R.id.instagram_link, R.id.twitter_link, R.id.changelog, + R.id.open_source, R.id.discord_link, R.id.telegram_link}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.faq_link: + openUrl(Constants.FAQ_LINK); + break; + case R.id.telegram_link: + openUrl(Constants.APP_TELEGRAM_LINK); + break; + case R.id.discord_link: + openUrl(Constants.DISCORD_LINK); + break; + case R.id.app_github: + openUrl(GITHUB_PROJECT); + break; + case R.id.app_google_plus: + openUrl(GOOGLE_PLUS_COMMUNITY); + break; + case R.id.support_container: + startActivity(new Intent(this, SupportDevelopmentActivity.class)); + break; + case R.id.app_translation: + openUrl(TRANSLATE); + break; + case R.id.app_rate: + openUrl(RATE_ON_GOOGLE_PLAY); + break; + case R.id.app_share: + shareApp(); + break; + case R.id.pro_container: + NavigationUtil.goToProVersion(this); + break; + + case R.id.instagram_link: + openUrl(APP_INSTAGRAM_LINK); + break; + case R.id.twitter_link: + openUrl(APP_TWITTER_LINK); + break; + case R.id.changelog: + openUrl(TELEGRAM_CHANGE_LOG); + break; + case R.id.open_source: + NavigationUtil.goToOpenSource(this); + break; + } + } + + private String getAppVersion() { + try { + PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + return packageInfo.versionName; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return "0.0.0"; + } + } + + private void shareApp() { + Intent shareIntent = ShareCompat.IntentBuilder.from(this) + .setType("songText/plain") + .setText(String.format(getString(R.string.app_share), getPackageName())) + .getIntent(); + if (shareIntent.resolveActivity(getPackageManager()) != null) { + startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.action_share))); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.java new file mode 100644 index 00000000..15febb95 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.java @@ -0,0 +1,414 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CollapsingToolbarLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.NestedScrollView; +import android.support.v7.widget.AppCompatTextView; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupMenu; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog; +import code.name.monkey.retromusic.dialogs.DeleteSongsDialog; +import code.name.monkey.retromusic.glide.ArtistGlideRequest; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder; +import code.name.monkey.retromusic.loaders.ArtistLoader; +import code.name.monkey.retromusic.misc.AppBarStateChangeListener; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.contract.AlbumDetailsContract; +import code.name.monkey.retromusic.mvp.presenter.AlbumDetailsPresenter; +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; +import code.name.monkey.retromusic.ui.activities.tageditor.AbsTagEditorActivity; +import code.name.monkey.retromusic.ui.activities.tageditor.AlbumTagEditorActivity; +import code.name.monkey.retromusic.ui.adapter.album.AlbumAdapter; +import code.name.monkey.retromusic.ui.adapter.album.HorizontalAlbumAdapter; +import code.name.monkey.retromusic.ui.adapter.song.SimpleSongAdapter; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +public class AlbumDetailsActivity extends AbsSlidingMusicPanelActivity implements + AlbumDetailsContract.AlbumDetailsView { + + public static final String EXTRA_ALBUM_ID = "extra_album_id"; + private static final int TAG_EDITOR_REQUEST = 2001; + @BindView(R.id.image) + ImageView image; + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + @BindView(R.id.title) + TextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.song_title) + AppCompatTextView songTitle; + @BindView(R.id.action_shuffle_all) + FloatingActionButton shuffleButton; + @BindView(R.id.collapsing_toolbar) + @Nullable + CollapsingToolbarLayout collapsingToolbarLayout; + @BindView(R.id.app_bar) + @Nullable + AppBarLayout appBarLayout; + @BindView(R.id.image_container) + @Nullable + View imageContainer; + @BindView(R.id.content) + View contentContainer; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.more_recycler_view) + RecyclerView moreRecyclerView; + @BindView(R.id.more_title) + TextView moreTitle; + @BindView(R.id.artist_image) + ImageView artistImage; + + private AlbumDetailsPresenter albumDetailsPresenter; + private Album album; + private SimpleSongAdapter adapter; + + @Override + protected View createContentView() { + return wrapSlidingMusicPanel(R.layout.activity_album); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + setDrawUnderStatusbar(true); + super.onCreate(savedInstanceState); + ButterKnife.bind(this); + + supportPostponeEnterTransition(); + setupToolbarMarginHeight(); + setBottomBarVisibility(View.GONE); + setLightNavigationBar(true); + setNavigationbarColorAuto(); + + int albumId = getIntent().getIntExtra(EXTRA_ALBUM_ID, -1); + albumDetailsPresenter = new AlbumDetailsPresenter(this, albumId); + + } + + private void setupToolbarMarginHeight() { + int primaryColor = ThemeStore.primaryColor(this); + TintHelper.setTintAuto(contentContainer, primaryColor, true); + if (collapsingToolbarLayout != null) { + collapsingToolbarLayout.setContentScrimColor(primaryColor); + collapsingToolbarLayout.setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor)); + } + + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(null); + + if (toolbar != null && !PreferenceUtil.getInstance(this).getFullScreenMode()) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams(); + params.topMargin = RetroUtil.getStatusBarHeight(this); + toolbar.setLayoutParams(params); + } + + if (appBarLayout != null) { + appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { + @Override + public void onStateChanged(AppBarLayout appBarLayout, State state) { + int color; + switch (state) { + case COLLAPSED: + setLightStatusbar(!ATHUtil.isWindowBackgroundDark(AlbumDetailsActivity.this)); + color = ATHUtil.resolveColor(AlbumDetailsActivity.this, R.attr.iconColor); + break; + default: + case EXPANDED: + case IDLE: + setLightStatusbar(false); + color = ContextCompat.getColor(AlbumDetailsActivity.this, R.color.md_white_1000); + break; + } + ToolbarContentTintHelper.colorizeToolbar(toolbar, color, AlbumDetailsActivity.this); + } + }); + } + + } + + @OnClick({R.id.action_shuffle_all}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.menu_close: + onBackPressed(); + break; + case R.id.menu: + PopupMenu popupMenu = new PopupMenu(this, view); + popupMenu.inflate(R.menu.menu_album_detail); + MenuItem sortOrder = popupMenu.getMenu().findItem(R.id.action_sort_order); + setUpSortOrderMenu(sortOrder.getSubMenu()); + popupMenu.setOnMenuItemClickListener(this::onOptionsItemSelected); + popupMenu.show(); + break; + case R.id.action_shuffle_all: + MusicPlayerRemote.openAndShuffleQueue(album.songs, true); + break; + } + } + + @Override + protected void onResume() { + super.onResume(); + albumDetailsPresenter.subscribe(); + } + + @Override + protected void onPause() { + super.onPause(); + albumDetailsPresenter.unsubscribe(); + } + + @Override + public void loading() { + + } + + @Override + public void showEmptyView() { + + } + + @Override + public void completed() { + supportStartPostponedEnterTransition(); + } + + @Override + public void showData(Album album) { + if (album.songs.isEmpty()) { + finish(); + return; + } + this.album = album; + + title.setText(album.getTitle()); + text.setText(String.format("%s%s • %s", album.getArtistName(), " • " + MusicUtil.getYearString(album.getYear()), + MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, album.songs)))); + + loadAlbumCover(); + + adapter = new SimpleSongAdapter(this, this.album.songs, R.layout.item_song); + + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setItemAnimator(new DefaultItemAnimator()); + recyclerView.setAdapter(adapter); + recyclerView.setNestedScrollingEnabled(false); + + loadMoreFrom(album); + + } + + private void loadMoreFrom(Album album) { + + ArtistGlideRequest.Builder.from(Glide.with(this), + ArtistLoader.getArtist(this, album.getArtistId()).blockingFirst()) + .forceDownload(true) + .generatePalette(this).build() + .dontAnimate() + .into(new RetroMusicColoredTarget(artistImage) { + @Override + public void onColorReady(int color) { + setColors(color); + } + }); + + + ArrayList albums = ArtistLoader.getArtist(this, album.getArtistId()).blockingFirst().albums; + albums.remove(album); + if (!albums.isEmpty()) { + moreTitle.setVisibility(View.VISIBLE); + moreRecyclerView.setVisibility(View.VISIBLE); + } else { + return; + } + moreTitle.setText(String.format("More from %s", album.getArtistName())); + + AlbumAdapter albumAdapter = new HorizontalAlbumAdapter(this, albums, + false, null); + moreRecyclerView.setLayoutManager(new GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false)); + moreRecyclerView.setAdapter(albumAdapter); + } + + public Album getAlbum() { + return album; + } + + private void loadAlbumCover() { + SongGlideRequest.Builder.from(Glide.with(this), getAlbum().safeGetFirstSong()) + .checkIgnoreMediaStore(this) + .generatePalette(this).build() + .dontAnimate() + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, Object model, Target target, + boolean isFirstResource) { + supportStartPostponedEnterTransition(); + return false; + } + + @Override + public boolean onResourceReady(BitmapPaletteWrapper resource, Object model, + Target target, boolean isFromMemoryCache, + boolean isFirstResource) { + supportStartPostponedEnterTransition(); + return false; + } + }) + .into(new RetroMusicColoredTarget(image) { + @Override + public void onColorReady(int color) { + setColors(color); + } + }); + } + + private void setColors(int color) { + int themeColor = PreferenceUtil.getInstance(this).getAdaptiveColor() ? color : ThemeStore.accentColor(this); + songTitle.setTextColor(themeColor); + moreTitle.setTextColor(themeColor); + + TintHelper.setTintAuto(shuffleButton, themeColor, true); + findViewById(R.id.root).setBackgroundColor(ThemeStore.primaryColor(this)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_album_detail, menu); + MenuItem sortOrder = menu.findItem(R.id.action_sort_order); + setUpSortOrderMenu(sortOrder.getSubMenu()); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return handleSortOrderMenuItem(item); + } + + private boolean handleSortOrderMenuItem(@NonNull MenuItem item) { + String sortOrder = null; + final ArrayList songs = adapter.getDataSet(); + switch (item.getItemId()) { + case R.id.action_play_next: + MusicPlayerRemote.playNext(songs); + return true; + case R.id.action_add_to_current_playing: + MusicPlayerRemote.enqueue(songs); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialog.create(songs).show(getSupportFragmentManager(), "ADD_PLAYLIST"); + return true; + case R.id.action_delete_from_device: + DeleteSongsDialog.create(songs).show(getSupportFragmentManager(), "DELETE_SONGS"); + return true; + case android.R.id.home: + super.onBackPressed(); + return true; + case R.id.action_tag_editor: + Intent intent = new Intent(this, AlbumTagEditorActivity.class); + intent.putExtra(AbsTagEditorActivity.EXTRA_ID, getAlbum().getId()); + startActivityForResult(intent, TAG_EDITOR_REQUEST); + return true; + case R.id.action_go_to_artist: + NavigationUtil.goToArtist(this, getAlbum().getArtistId()); + return true; + /*Sort*/ + case R.id.action_sort_order_title: + sortOrder = AlbumSongSortOrder.SONG_A_Z; + break; + case R.id.action_sort_order_title_desc: + sortOrder = AlbumSongSortOrder.SONG_Z_A; + break; + case R.id.action_sort_order_track_list: + sortOrder = AlbumSongSortOrder.SONG_TRACK_LIST; + break; + case R.id.action_sort_order_artist_song_duration: + sortOrder = AlbumSongSortOrder.SONG_DURATION; + break; + } + if (sortOrder != null) { + item.setChecked(true); + setSaveSortOrder(sortOrder); + } + return true; + } + + private String getSavedSortOrder() { + return PreferenceUtil.getInstance(this).getAlbumDetailSongSortOrder(); + } + + private void setUpSortOrderMenu(@NonNull SubMenu sortOrder) { + switch (getSavedSortOrder()) { + case AlbumSongSortOrder.SONG_A_Z: + sortOrder.findItem(R.id.action_sort_order_title).setChecked(true); + break; + case AlbumSongSortOrder.SONG_Z_A: + sortOrder.findItem(R.id.action_sort_order_title_desc).setChecked(true); + break; + case AlbumSongSortOrder.SONG_TRACK_LIST: + sortOrder.findItem(R.id.action_sort_order_track_list).setChecked(true); + break; + case AlbumSongSortOrder.SONG_DURATION: + sortOrder.findItem(R.id.action_sort_order_artist_song_duration).setChecked(true); + break; + } + } + + private void setSaveSortOrder(String sortOrder) { + PreferenceUtil.getInstance(this).setAlbumDetailSongSortOrder(sortOrder); + reload(); + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + reload(); + } + + private void reload() { + albumDetailsPresenter.subscribe(); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.java new file mode 100755 index 00000000..de8372b5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ArtistDetailActivity.java @@ -0,0 +1,429 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.content.Intent; +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CollapsingToolbarLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatTextView; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.text.Html; +import android.text.Spanned; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; + +import java.util.ArrayList; +import java.util.Locale; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog; +import code.name.monkey.retromusic.glide.ArtistGlideRequest; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.misc.AppBarStateChangeListener; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.contract.ArtistDetailContract; +import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsPresenter; +import code.name.monkey.retromusic.rest.LastFMRestClient; +import code.name.monkey.retromusic.rest.model.LastFmArtist; +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; +import code.name.monkey.retromusic.ui.adapter.album.AlbumAdapter; +import code.name.monkey.retromusic.ui.adapter.album.HorizontalAlbumAdapter; +import code.name.monkey.retromusic.ui.adapter.song.SimpleSongAdapter; +import code.name.monkey.retromusic.util.CustomArtistImageUtil; +import code.name.monkey.retromusic.util.DensityUtil; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implements + ArtistDetailContract.ArtistsDetailsView { + + public static final String EXTRA_ARTIST_ID = "extra_artist_id"; + private static final int REQUEST_CODE_SELECT_IMAGE = 9003; + @BindView(R.id.image) + ImageView image; + @BindView(R.id.biography) + TextView biographyTextView; + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + @BindView(R.id.album_recycler_view) + RecyclerView albumRecyclerView; + @BindView(R.id.album_title) + AppCompatTextView albumTitle; + @BindView(R.id.song_title) + AppCompatTextView songTitle; + @BindView(R.id.biography_title) + AppCompatTextView biographyTitle; + @BindView(R.id.title) + TextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.action_shuffle_all) + FloatingActionButton shuffleButton; + @BindView(R.id.collapsing_toolbar) + @Nullable + CollapsingToolbarLayout collapsingToolbarLayout; + @BindView(R.id.app_bar) + @Nullable + AppBarLayout appBarLayout; + @BindView(R.id.gradient_background) + @Nullable + View background; + @BindView(R.id.image_container) + @Nullable + View imageContainer; + @BindView(R.id.content) + View contentContainer; + @BindView(R.id.toolbar) + Toolbar toolbar; + @Nullable + private Spanned biography; + private Artist artist; + private LastFMRestClient lastFMRestClient; + private ArtistDetailsPresenter artistDetailsPresenter; + private SimpleSongAdapter songAdapter; + private AlbumAdapter albumAdapter; + private boolean forceDownload; + + @Override + protected View createContentView() { + return wrapSlidingMusicPanel(R.layout.activity_artist_details); + } + + @Override + protected void onCreate(Bundle bundle) { + setDrawUnderStatusbar(true); + super.onCreate(bundle); + ButterKnife.bind(this); + + supportPostponeEnterTransition(); + setBottomBarVisibility(View.GONE); + setNavigationbarColorAuto(); + setLightNavigationBar(true); + + lastFMRestClient = new LastFMRestClient(this); + + setUpViews(); + + int artistID = getIntent().getIntExtra(EXTRA_ARTIST_ID, -1); + artistDetailsPresenter = new ArtistDetailsPresenter(this, artistID); + } + + private void setUpViews() { + setupRecyclerView(); + setupToolbar(); + setupContainerHeight(); + } + + private void setupContainerHeight() { + if (imageContainer != null) { + LayoutParams params = imageContainer.getLayoutParams(); + params.width = DensityUtil.getScreenHeight(this) / 2; + imageContainer.setLayoutParams(params); + } + } + + private void setupToolbar() { + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(null); + + + if (toolbar != null && !PreferenceUtil.getInstance(this).getFullScreenMode()) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams(); + params.topMargin = RetroUtil.getStatusBarHeight(this); + toolbar.setLayoutParams(params); + } + + + int primaryColor = ThemeStore.primaryColor(this); + TintHelper.setTintAuto(contentContainer, primaryColor, true); + + if (appBarLayout != null) { + appBarLayout.setBackgroundColor(primaryColor); + appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { + @Override + public void onStateChanged(AppBarLayout appBarLayout, AppBarStateChangeListener.State state) { + int color; + switch (state) { + case COLLAPSED: + setLightStatusbar(!ATHUtil.isWindowBackgroundDark(ArtistDetailActivity.this)); + color = ATHUtil.resolveColor(ArtistDetailActivity.this, R.attr.iconColor); + break; + default: + case EXPANDED: + case IDLE: + setLightStatusbar(false); + color = ContextCompat.getColor(ArtistDetailActivity.this, R.color.md_white_1000); + break; + } + ToolbarContentTintHelper.colorizeToolbar(toolbar, color, ArtistDetailActivity.this); + } + }); + } + if (collapsingToolbarLayout != null) { + collapsingToolbarLayout.setContentScrimColor(primaryColor); + collapsingToolbarLayout.setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor)); + } + } + + private void setupRecyclerView() { + albumAdapter = new HorizontalAlbumAdapter(this, new ArrayList<>(), false, null); + albumRecyclerView.setItemAnimator(new DefaultItemAnimator()); + albumRecyclerView + .setLayoutManager(new GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false)); + albumRecyclerView.setAdapter(albumAdapter); + + songAdapter = new SimpleSongAdapter(this, new ArrayList<>(), R.layout.item_song); + recyclerView.setItemAnimator(new DefaultItemAnimator()); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(songAdapter); + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_CODE_SELECT_IMAGE: + if (resultCode == RESULT_OK) { + CustomArtistImageUtil.getInstance(this).setCustomArtistImage(artist, data.getData()); + } + break; + default: + if (resultCode == RESULT_OK) { + reload(); + } + break; + } + } + + @Override + protected void onResume() { + super.onResume(); + artistDetailsPresenter.subscribe(); + } + + @Override + protected void onPause() { + super.onPause(); + artistDetailsPresenter.unsubscribe(); + } + + @Override + public void loading() { + } + + @Override + public void showEmptyView() { + + } + + @Override + public void completed() { + supportStartPostponedEnterTransition(); + } + + @Override + public void showData(Artist artist) { + setArtist(artist); + } + + private Artist getArtist() { + if (artist == null) { + artist = new Artist(); + } + return artist; + } + + private void setArtist(Artist artist) { + if (artist.getSongCount() <= 0) { + finish(); + } + this.artist = artist; + loadArtistImage(); + + if (RetroUtil.isAllowedToDownloadMetadata(this)) { + loadBiography(); + } + title.setText(artist.getName()); + text.setText(String.format("%s • %s", MusicUtil.getArtistInfoString(this, artist), MusicUtil + .getReadableDurationString(MusicUtil.getTotalDuration(this, artist.getSongs())))); + + songAdapter.swapDataSet(artist.getSongs()); + albumAdapter.swapDataSet(artist.albums); + } + + private void loadBiography() { + loadBiography(Locale.getDefault().getLanguage()); + } + + private void loadBiography(@Nullable final String lang) { + biography = null; + + lastFMRestClient.getApiService() + .getArtistInfo(getArtist().getName(), lang, null) + .enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, + @NonNull Response response) { + final LastFmArtist lastFmArtist = response.body(); + if (lastFmArtist != null && lastFmArtist.getArtist() != null) { + final String bioContent = lastFmArtist.getArtist().getBio().getContent(); + if (bioContent != null && !bioContent.trim().isEmpty()) { + //TransitionManager.beginDelayedTransition(titleContainer); + biographyTextView.setVisibility(View.VISIBLE); + biographyTitle.setVisibility(View.VISIBLE); + biography = Html.fromHtml(bioContent); + biographyTextView.setText(biography); + } + } + + // If the "lang" parameter is set and no biography is given, retry with default language + if (biography == null && lang != null) { + loadBiography(null); + return; + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + t.printStackTrace(); + biography = null; + } + }); + } + + @OnClick(R.id.biography) + void toggleArtistBiography() { + if (biographyTextView.getMaxLines() == 4) { + biographyTextView.setMaxLines(Integer.MAX_VALUE); + } else { + biographyTextView.setMaxLines(4); + } + } + + private void loadArtistImage() { + ArtistGlideRequest.Builder.from(Glide.with(this), artist) + .forceDownload(forceDownload) + .generatePalette(this).build() + .dontAnimate() + .into(new RetroMusicColoredTarget(image) { + @Override + public void onColorReady(int color) { + setColors(color); + } + }); + forceDownload = false; + } + + private void setColors(int color) { + + int textColor = + PreferenceUtil.getInstance(this).getAdaptiveColor() ? color : ThemeStore.accentColor(this); + + albumTitle.setTextColor(textColor); + songTitle.setTextColor(textColor); + biographyTitle.setTextColor(textColor); + + TintHelper.setTintAuto(shuffleButton, textColor, true); + + if (background != null) { + background.setBackgroundTintList(ColorStateList.valueOf(color)); + } + findViewById(R.id.root).setBackgroundColor(ThemeStore.primaryColor(this)); + } + + @OnClick({R.id.action_shuffle_all}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.action_shuffle_all: + MusicPlayerRemote.openAndShuffleQueue(getArtist().getSongs(), true); + break; + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return handleSortOrderMenuItem(item); + } + + private boolean handleSortOrderMenuItem(@NonNull MenuItem item) { + final ArrayList songs = getArtist().getSongs(); + switch (item.getItemId()) { + case android.R.id.home: + super.onBackPressed(); + return true; + case R.id.action_play_next: + MusicPlayerRemote.playNext(songs); + return true; + case R.id.action_add_to_current_playing: + MusicPlayerRemote.enqueue(songs); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialog.create(songs).show(getSupportFragmentManager(), "ADD_PLAYLIST"); + return true; + case R.id.action_set_artist_image: + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + startActivityForResult( + Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), + REQUEST_CODE_SELECT_IMAGE); + return true; + case R.id.action_reset_artist_image: + Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), + Toast.LENGTH_SHORT).show(); + CustomArtistImageUtil.getInstance(ArtistDetailActivity.this).resetCustomArtistImage(artist); + forceDownload = true; + return true; + } + return true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_artist_detail, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + reload(); + } + + private void reload() { + artistDetailsPresenter.unsubscribe(); + artistDetailsPresenter.subscribe(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/EqualizerActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/EqualizerActivity.java new file mode 100644 index 00000000..83e1d19d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/EqualizerActivity.java @@ -0,0 +1,223 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.os.Bundle; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.SwitchCompat; +import android.support.v7.widget.Toolbar; +import android.transition.TransitionManager; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.Spinner; +import android.widget.TextView; +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.EqualizerHelper; +import code.name.monkey.retromusic.ui.activities.base.AbsMusicServiceActivity; + +/** + * @author Hemanth S (h4h13). + */ + +public class EqualizerActivity extends AbsMusicServiceActivity implements + AdapterView.OnItemSelectedListener { + + @BindView(R.id.equalizer) + SwitchCompat mEnable; + @BindView(R.id.content) + LinearLayout mContent; + @BindView(R.id.bands) + LinearLayout mLinearLayout; + + @BindView(R.id.bass_boost_strength) + SeekBar mBassBoostStrength; + @BindView(R.id.virtualizer_strength) + SeekBar mVirtualizerStrength; + + @BindView(R.id.bass_boost) + TextView mBassBoost; + @BindView(R.id.virtualizer) + TextView mVirtualizer; + + @BindView(R.id.toolbar) + Toolbar mToolbar; + @BindView(R.id.app_bar) + AppBarLayout mAppBar; + @BindView(R.id.presets) + Spinner mPresets; + + private CompoundButton.OnCheckedChangeListener mListener = (buttonView, isChecked) -> { + switch (buttonView.getId()) { + case R.id.equalizer: + EqualizerHelper.getInstance().getEqualizer().setEnabled(isChecked); + TransitionManager.beginDelayedTransition(mContent); + mContent.setVisibility(isChecked ? View.VISIBLE : View.GONE); + break; + } + }; + private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + if (seekBar == mBassBoostStrength) { + mBassBoost.setEnabled(progress > 0); + EqualizerHelper.getInstance().setBassBoostStrength(progress); + EqualizerHelper.getInstance().setBassBoostEnabled(progress > 0); + } else if (seekBar == mVirtualizerStrength) { + mVirtualizer.setEnabled(progress > 0); + EqualizerHelper.getInstance().setVirtualizerEnabled(progress > 0); + EqualizerHelper.getInstance().setVirtualizerStrength(progress); + } + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }; + private ArrayAdapter mPresetsNamesAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_equalizer); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + int primaryColor = ThemeStore.primaryColor(this); + mToolbar.setBackgroundColor(primaryColor); + mAppBar.setBackgroundColor(primaryColor); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onBackPressed(); + } + }); + mToolbar.setTitle(R.string.equalizer); + setSupportActionBar(mToolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + mEnable.setChecked(EqualizerHelper.getInstance().getEqualizer().getEnabled()); + mEnable.setOnCheckedChangeListener(mListener); + + mPresetsNamesAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1); + mPresets.setAdapter(mPresetsNamesAdapter); + mPresets.setOnItemSelectedListener(this); + + mBassBoostStrength.setProgress(EqualizerHelper.getInstance().getBassBoostStrength()); + mBassBoostStrength.setOnSeekBarChangeListener(mSeekBarChangeListener); + + mVirtualizerStrength.setProgress(EqualizerHelper.getInstance().getVirtualizerStrength()); + mVirtualizerStrength.setOnSeekBarChangeListener(mSeekBarChangeListener); + + setupUI(); + addPresets(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + } + return super.onOptionsItemSelected(item); + + } + + private void addPresets() { + mPresetsNamesAdapter.clear(); + mPresetsNamesAdapter.add("Custom"); + for (int j = 0; j < EqualizerHelper.getInstance().getEqualizer().getNumberOfPresets(); j++) { + mPresetsNamesAdapter + .add(EqualizerHelper.getInstance().getEqualizer().getPresetName((short) j)); + mPresetsNamesAdapter.notifyDataSetChanged(); + } + mPresets + .setSelection((int) EqualizerHelper.getInstance().getEqualizer().getCurrentPreset() + 1); + } + + private void setupUI() { + mLinearLayout.removeAllViews(); + short bands; + try { + // get number of supported bands + bands = (short) EqualizerHelper.getInstance().getNumberOfBands(); + + // for each of the supported bands, we will set up a slider from -10dB to 10dB boost/attenuation, + // as well as text labels to assist the user + for (short i = 0; i < bands; i++) { + final short band = i; + + View view = LayoutInflater.from(this).inflate(R.layout.retro_seekbar, mLinearLayout, false); + TextView freqTextView = view.findViewById(R.id.hurtz); + freqTextView.setText( + String.format("%d Hz", EqualizerHelper.getInstance().getCenterFreq((int) band) / 1000)); + + TextView minDbTextView = view.findViewById(R.id.minus_db); + minDbTextView + .setText(String.format("%d dB", EqualizerHelper.getInstance().getBandLevelLow() / 100)); + + TextView maxDbTextView = view.findViewById(R.id.plus_db); + maxDbTextView.setText( + String.format("%d dB", EqualizerHelper.getInstance().getBandLevelHigh() / 100)); + + SeekBar bar = view.findViewById(R.id.seekbar); + bar.setMax(EqualizerHelper.getInstance().getBandLevelHigh() - EqualizerHelper.getInstance() + .getBandLevelLow()); + bar.setProgress( + EqualizerHelper.getInstance().getBandLevel((int) band) - EqualizerHelper.getInstance() + .getBandLevelLow()); + bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + EqualizerHelper.getInstance().setBandLevel((int) band, + (int) (progress + EqualizerHelper.getInstance().getBandLevelLow())); + if (fromUser) { + mPresets.setSelection(0); + } + } + + public void onStartTrackingTouch(SeekBar seekBar) { + } + + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + + mLinearLayout.addView(view); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (position == 0) { + return; + } + EqualizerHelper.getInstance().getEqualizer().usePreset((short) (position - 1)); + setupUI(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.java new file mode 100644 index 00000000..bd1d0742 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/GenreDetailsActivity.java @@ -0,0 +1,272 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CollapsingToolbarLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.afollestad.materialcab.MaterialCab; +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.menu.GenreMenuHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.misc.AppBarStateChangeListener; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.contract.GenreDetailsContract; +import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter; +import code.name.monkey.retromusic.ui.adapter.song.SongAdapter; +import code.name.monkey.retromusic.util.RetroColorUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.util.ViewUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class GenreDetailsActivity extends AbsSlidingMusicPanelActivity implements + GenreDetailsContract.GenreDetailsView, CabHolder { + public static final String EXTRA_GENRE_ID = "extra_genre_id"; + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + + @BindView(R.id.toolbar) + Toolbar toolbar; + + @BindView(android.R.id.empty) + TextView empty; + + @BindView(R.id.status_bar) + View statusBar; + + @BindView(R.id.action_shuffle) + FloatingActionButton shuffleButton; + + @BindView(R.id.progress_bar) + ProgressBar progressBar; + + @BindView(R.id.app_bar) + AppBarLayout appBarLayout; + + @BindView(R.id.collapsing_toolbar) + CollapsingToolbarLayout toolbarLayout; + + private Genre genre; + private GenreDetailsPresenter presenter; + private SongAdapter songAdapter; + private MaterialCab cab; + + private void checkIsEmpty() { + empty.setVisibility( + songAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE + ); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + setDrawUnderStatusbar(true); + super.onCreate(savedInstanceState); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + setBottomBarVisibility(View.GONE); + + RetroUtil.statusBarHeight(statusBar); + + + genre = getIntent().getParcelableExtra(EXTRA_GENRE_ID); + presenter = new GenreDetailsPresenter(this, genre.id); + + setUpToolBar(); + setupRecyclerView(); + } + + @OnClick({R.id.action_shuffle}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.action_shuffle: + MusicPlayerRemote.openAndShuffleQueue(songAdapter.getDataSet(), true); + break; + } + } + + private void setUpToolBar() { + int primaryColor = ThemeStore.primaryColor(this); + toolbar.setBackgroundColor(primaryColor); + appBarLayout.setBackgroundColor(primaryColor); + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(genre.name); + setTitle(R.string.app_name); + + appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { + @Override + public void onStateChanged(AppBarLayout appBarLayout, AppBarStateChangeListener.State state) { + int color; + switch (state) { + default: + case COLLAPSED: + case EXPANDED: + case IDLE: + color = ATHUtil.resolveColor(GenreDetailsActivity.this, android.R.attr.textColorPrimary); + break; + } + toolbarLayout.setExpandedTitleColor(color); + ToolbarContentTintHelper.colorizeToolbar(toolbar, color, GenreDetailsActivity.this); + } + }); + + TintHelper.setTintAuto(shuffleButton, ThemeStore.accentColor(this), true); + } + + @Override + protected void onResume() { + super.onResume(); + presenter.subscribe(); + } + + @Override + protected void onPause() { + super.onPause(); + presenter.unsubscribe(); + } + + @Override + protected View createContentView() { + return wrapSlidingMusicPanel(R.layout.activity_playlist_detail); + } + + + @Override + public void loading() { + + } + + @Override + public void showEmptyView() { + + } + + @Override + public void completed() { + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_genre_detail, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + } + return GenreMenuHelper.handleMenuClick(this, genre, item); + } + + private void setupRecyclerView() { + ViewUtil.setUpFastScrollRecyclerViewColor(this, + ((FastScrollRecyclerView) recyclerView), ThemeStore.accentColor(this)); + songAdapter = new SongAdapter(this, new ArrayList(), R.layout.item_list, false, this); + recyclerView.setItemAnimator(new DefaultItemAnimator()); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(songAdapter); + + songAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + } + }); + } + + @Override + public void showData(ArrayList songs) { + songAdapter.swapDataSet(songs); + } + + public void showHeartAnimation() { + shuffleButton.clearAnimation(); + + shuffleButton.setScaleX(0.9f); + shuffleButton.setScaleY(0.9f); + shuffleButton.setVisibility(View.VISIBLE); + shuffleButton.setPivotX(shuffleButton.getWidth() / 2); + shuffleButton.setPivotY(shuffleButton.getHeight() / 2); + + shuffleButton.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> shuffleButton.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @NonNull + @Override + public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) cab.finish(); + cab = new MaterialCab(this, R.id.cab_stub) + .setMenu(menu) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this))) + .start(callback); + return cab; + } + + @Override + public void onBackPressed() { + if (cab != null && cab.isActive()) cab.finish(); + else { + recyclerView.stopScroll(); + super.onBackPressed(); + } + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + presenter.subscribe(); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/LicenseActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LicenseActivity.java new file mode 100644 index 00000000..0efcfac5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LicenseActivity.java @@ -0,0 +1,52 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.webkit.WebView; +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; + +public class LicenseActivity extends AbsBaseActivity { + @BindView(R.id.license) + WebView mLicense; + @BindView(R.id.toolbar) + Toolbar mToolbar; + @BindView(R.id.appbar) + AppBarLayout mAppbar; + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_license); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + mLicense.loadUrl("file:///android_asset/index.html"); + + mToolbar.setTitle(R.string.licenses); + mToolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + mToolbar.setNavigationOnClickListener(view -> onBackPressed()); + mToolbar.setBackgroundColor(ThemeStore.primaryColor(this)); + mAppbar.setBackgroundColor(ThemeStore.primaryColor(this)); + setSupportActionBar(mToolbar); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/LockScreenActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LockScreenActivity.java new file mode 100644 index 00000000..8a993189 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LockScreenActivity.java @@ -0,0 +1,86 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.os.Bundle; +import android.support.v4.view.ViewCompat; +import android.view.WindowManager; + +import com.bumptech.glide.Glide; +import com.r0adkll.slidr.Slidr; +import com.r0adkll.slidr.model.SlidrConfig; +import com.r0adkll.slidr.model.SlidrPosition; + +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.activities.base.AbsMusicServiceActivity; +import code.name.monkey.retromusic.ui.fragments.player.lockscreen.LockScreenPlayerControlsFragment; + +public class LockScreenActivity extends AbsMusicServiceActivity { + private LockScreenPlayerControlsFragment mFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + + setDrawUnderStatusbar(true); + setContentView(R.layout.activity_lock_screen_old_style); + + hideStatusBar(); + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + SlidrConfig config = new SlidrConfig.Builder() + .position(SlidrPosition.BOTTOM) + .build(); + + Slidr.attach(this, config); + + ButterKnife.bind(this); + mFragment = (LockScreenPlayerControlsFragment) getSupportFragmentManager().findFragmentById(R.id.playback_controls_fragment); + + findViewById(R.id.slide).setTranslationY(100f); + findViewById(R.id.slide).setAlpha(0f); + ViewCompat.animate(findViewById(R.id.slide)) + .translationY(0f) + .alpha(1f) + .setDuration(1500) + .start(); + + findViewById(R.id.root_layout).setBackgroundColor(ThemeStore.primaryColor(this)); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSongs(); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + updateSongs(); + } + + private void updateSongs() { + Song song = MusicPlayerRemote.getCurrentSong(); + SongGlideRequest.Builder.from(Glide.with(this), song) + .checkIgnoreMediaStore(this) + .generatePalette(this) + .build().into(new RetroMusicColoredTarget(findViewById(R.id.image)) { + @Override + public void onColorReady(int color) { + mFragment.setDark(color); + } + }); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/LyricsActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LyricsActivity.java new file mode 100644 index 00000000..3d388f85 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LyricsActivity.java @@ -0,0 +1,392 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.annotation.SuppressLint; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.Toolbar; +import android.text.InputType; +import android.text.TextUtils; +import android.view.MenuItem; +import android.view.View; +import android.view.WindowManager; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.TextView; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.bumptech.glide.Glide; + +import org.jaudiotagger.tag.FieldKey; + +import java.io.File; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.Map; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.model.lyrics.Lyrics; +import code.name.monkey.retromusic.ui.activities.base.AbsMusicServiceActivity; +import code.name.monkey.retromusic.ui.activities.tageditor.WriteTagsAsyncTask; +import code.name.monkey.retromusic.util.LyricUtil; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.views.LyricView; +import io.reactivex.disposables.CompositeDisposable; + +public class LyricsActivity extends AbsMusicServiceActivity implements MusicProgressViewUpdateHelper.Callback { + + @BindView(R.id.title) + TextView songTitle; + @BindView(R.id.text) + TextView songText; + @BindView(R.id.lyrics) + LyricView lyricView; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.offline_lyrics) + TextView offlineLyrics; + @BindView(R.id.actions) + RadioGroup actionsLayout; + @BindView(R.id.gradient_background) + View background; + + private MusicProgressViewUpdateHelper updateHelper; + private AsyncTask updateLyricsAsyncTask; + private CompositeDisposable disposable; + private Song song; + private Lyrics lyrics; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_lyrics); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + updateHelper = new MusicProgressViewUpdateHelper(this, 500, 1000); + + setupToolbar(); + setupLyricsView(); + setupWakelock(); + + actionsLayout.setOnCheckedChangeListener((group, checkedId) -> selectLyricsTye(checkedId)); + } + + private void selectLyricsTye(int group) { + + RadioButton radioButton = actionsLayout.findViewById(group); + radioButton.setBackgroundTintList(ColorStateList.valueOf(ThemeStore.accentColor(this))); + radioButton.setTextColor(ThemeStore.textColorPrimary(this)); + + offlineLyrics.setVisibility(View.GONE); + lyricView.setVisibility(View.GONE); + switch (group) { + case R.id.synced_lyrics: + loadLRCLyrics(); + lyricView.setVisibility(View.VISIBLE); + break; + default: + case R.id.normal_lyrics: + loadSongLyrics(); + offlineLyrics.setVisibility(View.VISIBLE); + break; + } + } + + private void loadLRCLyrics() { + if (LyricUtil.isLrcFileExist(song.title, song.artistName)) { + showLyricsLocal(LyricUtil.getLocalLyricFile(song.title, song.artistName)); + } + } + + private void setupWakelock() { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + private void setupLyricsView() { + disposable = new CompositeDisposable(); + //lyricView.setLineSpace(15.0f); + //lyricView.setTextSize(17.0f); + //lyricView.setPlayable(true); + //lyricView.setTranslationY(DensityUtil.getScreenWidth(this) + DensityUtil.dip2px(this, 120)); + lyricView.setOnPlayerClickListener((progress, content) -> MusicPlayerRemote.seekTo((int) progress)); + + //lyricView.setHighLightTextColor(ThemeStore.accentColor(this)); + lyricView.setDefaultColor(ContextCompat.getColor(this, R.color.md_grey_400)); + //lyricView.setTouchable(false); + lyricView.setHintColor(Color.WHITE); + + + } + + private void setupToolbar() { + toolbar.setBackgroundColor(ThemeStore.primaryColor(this)); + toolbar.setTitle(""); + toolbar.setNavigationOnClickListener(v -> onBackPressed()); + setSupportActionBar(toolbar); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + loadLrcFile(); + } + + @Override + protected void onResume() { + super.onResume(); + updateHelper.start(); + } + + @Override + protected void onPause() { + super.onPause(); + updateHelper.stop(); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + loadLrcFile(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + disposable.clear(); + lyricView.setOnPlayerClickListener(null); + + if (updateLyricsAsyncTask != null && !updateLyricsAsyncTask.isCancelled()) { + updateLyricsAsyncTask.cancel(true); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + lyricView.setCurrentTimeMillis(progress); + } + + private void loadLrcFile() { + song = MusicPlayerRemote.getCurrentSong(); + songTitle.setText(song.title); + songText.setText(song.artistName); + SongGlideRequest.Builder.from(Glide.with(this), song) + .checkIgnoreMediaStore(this) + .generatePalette(this) + .build() + .into(new RetroMusicColoredTarget(findViewById(R.id.image)) { + @Override + public void onColorReady(int color) { + if (PreferenceUtil.getInstance(LyricsActivity.this).getAdaptiveColor()) { + background.setBackgroundColor(color); + } + } + }); + selectLyricsTye(actionsLayout.getCheckedRadioButtonId()); + } + + private void showLyricsLocal(File file) { + if (file == null) { + lyricView.reset(); + lyricView.setVisibility(View.GONE); + } else { + lyricView.setVisibility(View.VISIBLE); + lyricView.setLyricFile(file, "UTF-8"); + } + } + + @OnClick({R.id.edit_lyrics}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.edit_lyrics: + switch (actionsLayout.getCheckedRadioButtonId()) { + case R.id.synced_lyrics: + showSyncedLyrics(); + break; + case R.id.normal_lyrics: + showLyricsSaveDialog(); + break; + } + break; + } + } + + @SuppressLint("StaticFieldLeak") + private void loadSongLyrics() { + if (updateLyricsAsyncTask != null) updateLyricsAsyncTask.cancel(false); + final Song song = MusicPlayerRemote.getCurrentSong(); + updateLyricsAsyncTask = new AsyncTask() { + @Override + protected Lyrics doInBackground(Void... params) { + String data = MusicUtil.getLyrics(song); + if (TextUtils.isEmpty(data)) { + return null; + } + return Lyrics.parse(song, data); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + lyrics = null; + } + + @Override + protected void onPostExecute(Lyrics l) { + lyrics = l; + offlineLyrics.setVisibility(View.VISIBLE); + if (l == null) { + offlineLyrics.setText(R.string.no_lyrics_found); + return; + } + offlineLyrics.setText(l.data); + } + + @Override + protected void onCancelled(Lyrics s) { + onPostExecute(null); + } + }.execute(); + } + + private void showSyncedLyrics() { + String content = ""; + try { + content = LyricUtil.getStringFromFile(song.title, song.artistName); + } catch (Exception e) { + e.printStackTrace(); + } + new MaterialDialog.Builder(this) + .title("Add lyrics") + .neutralText("Search") + .content("Add time frame lyrics") + .negativeText("Delete") + .onNegative((dialog, which) -> { + LyricUtil.deleteLrcFile(song.title, song.artistName); + loadLrcFile(); + }) + .onNeutral((dialog, which) -> RetroUtil.openUrl(LyricsActivity.this, getGoogleSearchLrcUrl())) + .inputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE) + .input("Paste lyrics here", content, (dialog, input) -> { + LyricUtil.writeLrcToLoc(song.title, song.artistName, input.toString()); + loadLrcFile(); + }).show(); + } + + private String getGoogleSearchLrcUrl() { + String baseUrl = "http://www.google.com/search?"; + String query = song.title + "+" + song.artistName; + query = "q=" + query.replace(" ", "+") + " .lrc"; + baseUrl += query; + return baseUrl; + } + + private void showLyricsSaveDialog() { + String content = ""; + if (lyrics == null) { + content = ""; + } else { + content = lyrics.data; + } + new MaterialDialog.Builder(this) + .title("Add lyrics") + .neutralText("Search") + .onNeutral(new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + RetroUtil.openUrl(LyricsActivity.this, getGoogleSearchUrl(song.title, song.artistName)); + } + }) + .inputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE) + .input("Paste lyrics here", content, (dialog, input) -> { + Map fieldKeyValueMap = new EnumMap<>(FieldKey.class); + fieldKeyValueMap.put(FieldKey.LYRICS, input.toString()); + + new WriteTagsAsyncTask(LyricsActivity.this) + .execute(new WriteTagsAsyncTask.LoadingInfo(getSongPaths(song), fieldKeyValueMap, null)); + loadLrcFile(); + }) + .show(); + } + + private ArrayList getSongPaths(Song song) { + ArrayList paths = new ArrayList<>(1); + paths.add(song.data); + return paths; + } + + private String getGoogleSearchUrl(String title, String text) { + String baseUrl = "http://www.google.com/search?"; + String query = title + "+" + text; + query = "q=" + query.replace(" ", "+") + " lyrics"; + baseUrl += query; + return baseUrl; + } + + /* + private void loadLyricsWIki(String title, String artist) { + offlineLyrics.setVisibility(View.GONE); + if (lyricsWikiTask != null) { + lyricsWikiTask.cancel(false); + } + lyricsWikiTask = new ParseLyrics(new ParseLyrics.LyricsCallback() { + @Override + public void onShowLyrics(String lyrics) { + offlineLyrics.setVisibility(View.VISIBLE); + offlineLyrics.setText(lyrics); + } + + @Override + public void onError() { + loadSongLyrics(); + } + }).execute(title, artist); + } + + private void callAgain(final String title, final String artist) { + disposable.clear(); + disposable.add(loadLyrics.downloadLrcFile(title, artist, MusicPlayerRemote.getSongDurationMillis()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .doOnSubscribe(disposable -> { + refresh.startAnimation(rotateAnimation); + }) + .subscribe(this::showLyricsLocal, throwable -> { + refresh.clearAnimation(); + showLyricsLocal(null); + //loadLyricsWIki(songTitle, artist); + toggleSyncLyrics(View.GONE); + }, () -> { + refresh.clearAnimation(); + Toast.makeText(this, "Lyrics downloaded", Toast.LENGTH_SHORT).show(); + })); + } +*/ +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/MainActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/MainActivity.java new file mode 100644 index 00000000..71b12467 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/MainActivity.java @@ -0,0 +1,336 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomNavigationView; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.afollestad.materialdialogs.MaterialDialog; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.SearchQueryHelper; +import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks; +import code.name.monkey.retromusic.loaders.AlbumLoader; +import code.name.monkey.retromusic.loaders.ArtistLoader; +import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; +import code.name.monkey.retromusic.ui.fragments.mainactivity.LibraryFragment; +import code.name.monkey.retromusic.ui.fragments.mainactivity.home.HomeFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; +import io.reactivex.Observable; +import io.reactivex.disposables.CompositeDisposable; + +public class MainActivity extends AbsSlidingMusicPanelActivity implements + SharedPreferences.OnSharedPreferenceChangeListener, + BottomNavigationView.OnNavigationItemSelectedListener { + + public static final int APP_INTRO_REQUEST = 2323; + private static final String TAG = "MainActivity"; + private static final int APP_USER_INFO_REQUEST = 9003; + private static final int REQUEST_CODE_THEME = 9002; + + @Nullable + MainActivityFragmentCallbacks currentFragment; + + @BindView(R.id.parent_container) + FrameLayout drawerLayout; + + private boolean blockRequestPermissions; + private CompositeDisposable disposable = new CompositeDisposable(); + + private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null && action.equals(Intent.ACTION_SCREEN_OFF)) { + if (PreferenceUtil.getInstance(context).getLockScreen() && MusicPlayerRemote.isPlaying()) { + Intent activity = new Intent(context, LockScreenActivity.class); + activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + ActivityCompat.startActivity(context, activity, null); + } + } + } + }; + + + @Override + protected View createContentView() { + @SuppressLint("InflateParams") + View contentView = getLayoutInflater().inflate(R.layout.activity_main_drawer_layout, null); + ViewGroup drawerContent = contentView.findViewById(R.id.drawer_content_container); + drawerContent.addView(wrapSlidingMusicPanel(R.layout.activity_main_content)); + return contentView; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + setDrawUnderStatusbar(true); + super.onCreate(savedInstanceState); + + ButterKnife.bind(this); + + setBottomBarVisibility(View.VISIBLE); + + drawerLayout.setOnApplyWindowInsetsListener((view, windowInsets) -> + windowInsets.replaceSystemWindowInsets(0, 0, 0, 0)); + + if (savedInstanceState == null) { + setCurrentFragment(PreferenceUtil.getInstance(this).getLastPage()); + } else { + restoreCurrentFragment(); + } + /*if (!RetroApplication.isProVersion() && + !PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean("shown", false)) { + showPromotionalOffer(); + }*/ + getBottomNavigationView().setOnNavigationItemSelectedListener(this); + } + + @Override + protected void onResume() { + super.onResume(); + IntentFilter screenOnOff = new IntentFilter(); + screenOnOff.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(broadcastReceiver, screenOnOff); + + PreferenceUtil.getInstance(this).registerOnSharedPreferenceChangedListener(this); + + if (getIntent().hasExtra("expand")) { + if (getIntent().getBooleanExtra("expand", false)) { + expandPanel(); + getIntent().putExtra("expand", false); + } + } + + } + + @Override + public void onDestroy() { + super.onDestroy(); + disposable.clear(); + if (broadcastReceiver == null) { + return; + } + unregisterReceiver(broadcastReceiver); + PreferenceUtil.getInstance(this).unregisterOnSharedPreferenceChangedListener(this); + } + + public void setCurrentFragment(@Nullable Fragment fragment, boolean isStackAdd) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + fragmentTransaction.replace(R.id.fragment_container, fragment, TAG); + if (isStackAdd) { + fragmentTransaction.addToBackStack(TAG); + } + fragmentTransaction.commit(); + currentFragment = (MainActivityFragmentCallbacks) fragment; + } + + private void restoreCurrentFragment() { + currentFragment = (MainActivityFragmentCallbacks) getSupportFragmentManager() + .findFragmentById(R.id.fragment_container); + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + PreferenceUtil.getInstance(this).setLastPage(item.getItemId()); + disposable.add(Observable.just(item.getItemId()) + .throttleFirst(3, TimeUnit.SECONDS) + .subscribe(this::setCurrentFragment)); + return true; + } + + private void setCurrentFragment(int menuItem) { + switch (menuItem) { + case R.id.action_song: + case R.id.action_album: + case R.id.action_artist: + case R.id.action_playlist: + setCurrentFragment(LibraryFragment.newInstance(menuItem), false); + break; + default: + case R.id.action_home: + setCurrentFragment(HomeFragment.newInstance(), false); + break; + } + } + + private void handlePlaybackIntent(@Nullable Intent intent) { + if (intent == null) { + return; + } + + Uri uri = intent.getData(); + String mimeType = intent.getType(); + boolean handled = false; + + if (intent.getAction() != null && intent.getAction() + .equals(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH)) { + final ArrayList songs = SearchQueryHelper.getSongs(this, intent.getExtras()); + + if (MusicPlayerRemote.getShuffleMode() == MusicService.SHUFFLE_MODE_SHUFFLE) { + MusicPlayerRemote.openAndShuffleQueue(songs, true); + } else { + MusicPlayerRemote.openQueue(songs, 0, true); + } + handled = true; + } + + if (uri != null && uri.toString().length() > 0) { + MusicPlayerRemote.playFromUri(uri); + handled = true; + } else if (MediaStore.Audio.Playlists.CONTENT_TYPE.equals(mimeType)) { + final int id = (int) parseIdFromIntent(intent, "playlistId", "playlist"); + if (id >= 0) { + int position = intent.getIntExtra("position", 0); + ArrayList songs = new ArrayList<>( + PlaylistSongsLoader.getPlaylistSongList(this, id).blockingFirst()); + MusicPlayerRemote.openQueue(songs, position, true); + handled = true; + } + } else if (MediaStore.Audio.Albums.CONTENT_TYPE.equals(mimeType)) { + final int id = (int) parseIdFromIntent(intent, "albumId", "album"); + if (id >= 0) { + int position = intent.getIntExtra("position", 0); + MusicPlayerRemote + .openQueue(AlbumLoader.getAlbum(this, id).blockingFirst().songs, position, true); + handled = true; + } + } else if (MediaStore.Audio.Artists.CONTENT_TYPE.equals(mimeType)) { + final int id = (int) parseIdFromIntent(intent, "artistId", "artist"); + if (id >= 0) { + int position = intent.getIntExtra("position", 0); + MusicPlayerRemote.openQueue(ArtistLoader.getArtist(this, id).blockingFirst().getSongs(), position, true); + handled = true; + } + } + if (handled) { + setIntent(new Intent()); + } + } + + private long parseIdFromIntent(@NonNull Intent intent, String longKey, String stringKey) { + long id = intent.getLongExtra(longKey, -1); + if (id < 0) { + String idString = intent.getStringExtra(stringKey); + if (idString != null) { + try { + id = Long.parseLong(idString); + } catch (NumberFormatException e) { + Log.e(TAG, e.getMessage()); + } + } + } + return id; + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case APP_INTRO_REQUEST: + blockRequestPermissions = false; + if (!hasPermissions()) { + requestPermissions(); + } + + break; + case REQUEST_CODE_THEME: + case APP_USER_INFO_REQUEST: + postRecreate(); + break; + } + + } + + @Override + public boolean handleBackPress() { + return super.handleBackPress() || (currentFragment != null && + currentFragment.handleBackPress()); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + handlePlaybackIntent(getIntent()); + } + + @Override + protected void requestPermissions() { + if (!blockRequestPermissions) { + super.requestPermissions(); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + return super.onOptionsItemSelected(item); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equalsIgnoreCase(PreferenceUtil.GENERAL_THEME) || + key.equalsIgnoreCase(PreferenceUtil.ADAPTIVE_COLOR_APP) || + key.equalsIgnoreCase(PreferenceUtil.DOMINANT_COLOR) || + key.equalsIgnoreCase(PreferenceUtil.USER_NAME) || + key.equalsIgnoreCase(PreferenceUtil.TOGGLE_FULL_SCREEN) || + key.equalsIgnoreCase(PreferenceUtil.TOGGLE_VOLUME) || + key.equalsIgnoreCase(PreferenceUtil.TOGGLE_TAB_TITLES) || + key.equalsIgnoreCase(PreferenceUtil.ROUND_CORNERS) || + key.equals(PreferenceUtil.CAROUSEL_EFFECT) || + key.equals(PreferenceUtil.NOW_PLAYING_SCREEN_ID) || + key.equals(PreferenceUtil.TOGGLE_GENRE) || + key.equals(PreferenceUtil.BANNER_IMAGE_PATH) || + key.equals(PreferenceUtil.PROFILE_IMAGE_PATH) || + key.equals(PreferenceUtil.CIRCULAR_ALBUM_ART) || + key.equals(PreferenceUtil.KEEP_SCREEN_ON) || + key.equals(PreferenceUtil.TOGGLE_SEPARATE_LINE)) { + postRecreate(); + } + } + + private void showPromotionalOffer() { + new MaterialDialog.Builder(this) + .positiveText("Buy") + .onPositive((dialog, which) -> + startActivity(new Intent(MainActivity.this, ProVersionActivity.class))) + .negativeText(android.R.string.cancel) + .customView(R.layout.dialog_promotional_offer, false) + .dismissListener(dialog -> { + PreferenceManager.getDefaultSharedPreferences(MainActivity.this) + .edit() + .putBoolean("shown", true) + .apply(); + }) + .show(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlayingQueueActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlayingQueueActivity.java new file mode 100644 index 00000000..f29967a9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlayingQueueActivity.java @@ -0,0 +1,68 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.Toolbar; +import android.widget.TextView; +import butterknife.BindDrawable; +import butterknife.BindString; +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.ui.activities.base.AbsMusicServiceActivity; +import code.name.monkey.retromusic.ui.fragments.PlayingQueueFragment; +import code.name.monkey.retromusic.util.MusicUtil; + + +public class PlayingQueueActivity extends AbsMusicServiceActivity { + + @BindView(R.id.toolbar) + Toolbar mToolbar; + @BindDrawable(R.drawable.ic_close_white_24dp) + Drawable mClose; + @BindView(R.id.player_queue_sub_header) + TextView mPlayerQueueSubHeader; + @BindString(R.string.queue) + String queue; + @BindView(R.id.app_bar) + AppBarLayout mAppBarLayout; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_playing_queue); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + setupToolbar(); + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragment_container, new PlayingQueueFragment()) + .commit(); + } + } + + protected String getUpNextAndQueueTime() { + return getResources().getString(R.string.up_next) + " • " + MusicUtil + .getReadableDurationString( + MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.getPosition())); + } + + private void setupToolbar() { + mPlayerQueueSubHeader.setText(getUpNextAndQueueTime()); + mPlayerQueueSubHeader.setTextColor(ThemeStore.accentColor(this)); + mAppBarLayout.setBackgroundColor(ThemeStore.primaryColor(this)); + mToolbar.setBackgroundColor(ThemeStore.primaryColor(this)); + mToolbar.setNavigationIcon(mClose); + setSupportActionBar(mToolbar); + setTitle(queue); + mToolbar.setNavigationOnClickListener(v -> onBackPressed()); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.java new file mode 100644 index 00000000..1f383fcf --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/PlaylistDetailActivity.java @@ -0,0 +1,353 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CollapsingToolbarLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.TextView; + +import com.afollestad.materialcab.MaterialCab; +import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager; +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.loaders.PlaylistLoader; +import code.name.monkey.retromusic.misc.AppBarStateChangeListener; +import code.name.monkey.retromusic.model.AbsCustomPlaylist; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.PlaylistSong; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.contract.PlaylistSongsContract; +import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter; +import code.name.monkey.retromusic.ui.activities.base.AbsSlidingMusicPanelActivity; +import code.name.monkey.retromusic.ui.adapter.song.OrderablePlaylistSongAdapter; +import code.name.monkey.retromusic.ui.adapter.song.PlaylistSongAdapter; +import code.name.monkey.retromusic.ui.adapter.song.SongAdapter; +import code.name.monkey.retromusic.util.PlaylistsUtil; +import code.name.monkey.retromusic.util.RetroColorUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.util.ViewUtil; + +public class PlaylistDetailActivity extends AbsSlidingMusicPanelActivity implements CabHolder, + PlaylistSongsContract.PlaylistSongsView { + + @NonNull + public static String EXTRA_PLAYLIST = "extra_playlist"; + + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + + @BindView(R.id.toolbar) + Toolbar toolbar; + + @BindView(android.R.id.empty) + TextView empty; + + @BindView(R.id.action_shuffle) + FloatingActionButton shuffleButton; + + @BindView(R.id.app_bar) + AppBarLayout appBarLayout; + + @BindView(R.id.collapsing_toolbar) + CollapsingToolbarLayout toolbarLayout; + + @BindView(R.id.status_bar) + View statusBar; + + private Playlist playlist; + private MaterialCab cab; + private SongAdapter adapter; + private RecyclerView.Adapter wrappedAdapter; + private RecyclerViewDragDropManager recyclerViewDragDropManager; + private PlaylistSongsPresenter songsPresenter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setDrawUnderStatusbar(true); + super.onCreate(savedInstanceState); + ButterKnife.bind(this); + + RetroUtil.statusBarHeight(statusBar); + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + setBottomBarVisibility(View.GONE); + + playlist = getIntent().getExtras().getParcelable(EXTRA_PLAYLIST); + + if (playlist != null) { + songsPresenter = new PlaylistSongsPresenter(this, playlist); + } + + setUpToolBar(); + setUpRecyclerView(); + } + + public void showHeartAnimation() { + shuffleButton.clearAnimation(); + + shuffleButton.setScaleX(0.9f); + shuffleButton.setScaleY(0.9f); + shuffleButton.setVisibility(View.VISIBLE); + shuffleButton.setPivotX(shuffleButton.getWidth() / 2); + shuffleButton.setPivotY(shuffleButton.getHeight() / 2); + + shuffleButton.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> shuffleButton.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + + @Override + protected View createContentView() { + return wrapSlidingMusicPanel(R.layout.activity_playlist_detail); + } + + private void setUpRecyclerView() { + ViewUtil.setUpFastScrollRecyclerViewColor(this, ((FastScrollRecyclerView) recyclerView), + ThemeStore.accentColor(this)); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + if (playlist instanceof AbsCustomPlaylist) { + adapter = new PlaylistSongAdapter(this, new ArrayList(), R.layout.item_list, false, + this); + recyclerView.setAdapter(adapter); + } else { + recyclerViewDragDropManager = new RecyclerViewDragDropManager(); + final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator(); + adapter = new OrderablePlaylistSongAdapter(this, new ArrayList(), + R.layout.item_list, false, this, (fromPosition, toPosition) -> { + if (PlaylistsUtil + .moveItem(PlaylistDetailActivity.this, playlist.id, fromPosition, toPosition)) { + Song song = adapter.getDataSet().remove(fromPosition); + adapter.getDataSet().add(toPosition, song); + adapter.notifyItemMoved(fromPosition, toPosition); + } + }); + wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(adapter); + + recyclerView.setAdapter(wrappedAdapter); + recyclerView.setItemAnimator(animator); + + recyclerViewDragDropManager.attachRecyclerView(recyclerView); + } + adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + songsPresenter.subscribe(); + } + + private void setUpToolBar() { + int primaryColor = ThemeStore.primaryColor(this); + toolbar.setBackgroundColor(primaryColor); + appBarLayout.setBackgroundColor(primaryColor); + + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle(playlist.name); + + appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() { + @Override + public void onStateChanged(AppBarLayout appBarLayout, AppBarStateChangeListener.State state) { + int color; + switch (state) { + default: + case COLLAPSED: + case EXPANDED: + case IDLE: + color = ThemeStore.textColorPrimary(PlaylistDetailActivity.this); + break; + } + toolbarLayout.setExpandedTitleColor(color); + ToolbarContentTintHelper.colorizeToolbar(toolbar, color, PlaylistDetailActivity.this); + } + }); + TintHelper.setTintAuto(shuffleButton, ThemeStore.accentColor(this), true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate( + playlist instanceof AbsCustomPlaylist ? R.menu.menu_smart_playlist_detail + : R.menu.menu_playlist_detail, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + onBackPressed(); + return true; + } + return PlaylistMenuHelper.handleMenuClick(this, playlist, item); + } + + @NonNull + @Override + public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) { + cab.finish(); + } + cab = new MaterialCab(this, R.id.cab_stub) + .setMenu(menu) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor( + RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this))) + .start(callback); + return cab; + } + + @Override + public void onBackPressed() { + if (cab != null && cab.isActive()) { + cab.finish(); + } else { + recyclerView.stopScroll(); + super.onBackPressed(); + } + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + + if (!(playlist instanceof AbsCustomPlaylist)) { + // Playlist deleted + if (!PlaylistsUtil.doesPlaylistExist(this, playlist.id)) { + finish(); + return; + } + + // Playlist renamed + final String playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist.id); + if (!playlistName.equals(playlist.name)) { + playlist = PlaylistLoader.getPlaylist(this, playlist.id).blockingFirst(); + setToolbarTitle(playlist.name); + } + } + songsPresenter.subscribe(); + } + + private void setToolbarTitle(String title) { + //noinspection ConstantConditions + getSupportActionBar().setTitle(title); + } + + private void checkIsEmpty() { + empty.setVisibility( + adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE + ); + } + + @Override + public void onPause() { + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager.cancelDrag(); + } + super.onPause(); + songsPresenter.unsubscribe(); + } + + @Override + public void onDestroy() { + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager.release(); + recyclerViewDragDropManager = null; + } + + if (recyclerView != null) { + recyclerView.setItemAnimator(null); + recyclerView.setAdapter(null); + recyclerView = null; + } + + if (wrappedAdapter != null) { + WrapperAdapterUtils.releaseAll(wrappedAdapter); + wrappedAdapter = null; + } + adapter = null; + + super.onDestroy(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + songsPresenter.subscribe(); + } + + @Override + public void loading() { + } + + @Override + public void showEmptyView() { + empty.setVisibility(View.VISIBLE); + } + + @Override + public void completed() { + } + + @Override + public void showData(ArrayList songs) { + adapter.swapDataSet(songs); + } + + @OnClick(R.id.action_shuffle) + public void onViewClicked() { + showHeartAnimation(); + if (adapter.getDataSet().isEmpty()) { + return; + } + MusicPlayerRemote.openAndShuffleQueue(adapter.getDataSet(), true); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/ProVersionActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ProVersionActivity.java new file mode 100644 index 00000000..5381a064 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/ProVersionActivity.java @@ -0,0 +1,203 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.anjlab.android.iab.v3.BillingProcessor; +import com.anjlab.android.iab.v3.TransactionDetails; + +import java.lang.ref.WeakReference; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.BuildConfig; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; +import code.name.monkey.retromusic.util.RetroUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class ProVersionActivity extends AbsBaseActivity implements + BillingProcessor.IBillingHandler { + + private static final String TAG = "ProVersionActivity"; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.restore_button) + View restoreButton; + @BindView(R.id.purchase_button) + View purchaseButton; + @BindView(R.id.app_bar) + AppBarLayout appBar; + @BindView(R.id.status_bar) + View statusBar; + + private BillingProcessor billingProcessor; + private AsyncTask restorePurchaseAsyncTask; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_pro_version); + setDrawUnderStatusbar(true); + ButterKnife.bind(this); + RetroUtil.statusBarHeight(statusBar); + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + int primaryColor = ThemeStore.primaryColor(this); + toolbar.setBackgroundColor(primaryColor); + appBar.setBackgroundColor(primaryColor); + + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + toolbar.setNavigationOnClickListener(v -> onBackPressed()); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setTitle("RetroMusic Pro"); + + restoreButton.setEnabled(false); + purchaseButton.setEnabled(false); + + billingProcessor = new BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSE_KEY, this); + + } + + private void restorePurchase() { + if (restorePurchaseAsyncTask != null) { + restorePurchaseAsyncTask.cancel(false); + } + restorePurchaseAsyncTask = new RestorePurchaseAsyncTask(this).execute(); + } + + @OnClick({R.id.restore_button, R.id.purchase_button}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.restore_button: + if (restorePurchaseAsyncTask == null + || restorePurchaseAsyncTask.getStatus() != AsyncTask.Status.RUNNING) { + restorePurchase(); + } + break; + case R.id.purchase_button: + billingProcessor.purchase(ProVersionActivity.this, RetroApplication.PRO_VERSION_PRODUCT_ID); + break; + } + } + + @Override + public void onProductPurchased(@NonNull String productId, @Nullable TransactionDetails details) { + Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show(); + setResult(RESULT_OK); + } + + @Override + public void onPurchaseHistoryRestored() { + if (RetroApplication.isProVersion()) { + Toast.makeText(this, R.string.restored_previous_purchase_please_restart, Toast.LENGTH_LONG) + .show(); + setResult(RESULT_OK); + } else { + Toast.makeText(this, R.string.no_purchase_found, Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onBillingError(int errorCode, @Nullable Throwable error) { + Log.e(TAG, "Billing error: code = " + errorCode, error); + } + + + @Override + public void onBillingInitialized() { + restoreButton.setEnabled(true); + purchaseButton.setEnabled(true); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!billingProcessor.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + if (billingProcessor != null) { + billingProcessor.release(); + } + super.onDestroy(); + } + + private static class RestorePurchaseAsyncTask extends AsyncTask { + + private final WeakReference buyActivityWeakReference; + + RestorePurchaseAsyncTask(ProVersionActivity purchaseActivity) { + this.buyActivityWeakReference = new WeakReference<>(purchaseActivity); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + ProVersionActivity purchaseActivity = buyActivityWeakReference.get(); + if (purchaseActivity != null) { + Toast.makeText(purchaseActivity, R.string.restoring_purchase, Toast.LENGTH_SHORT).show(); + } else { + cancel(false); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + ProVersionActivity purchaseActivity = buyActivityWeakReference.get(); + if (purchaseActivity != null) { + return purchaseActivity.billingProcessor.loadOwnedPurchasesFromGoogle(); + } + cancel(false); + return null; + } + + @Override + protected void onPostExecute(Boolean b) { + super.onPostExecute(b); + ProVersionActivity purchaseActivity = buyActivityWeakReference.get(); + if (purchaseActivity == null || b == null) { + return; + } + + if (b) { + purchaseActivity.onPurchaseHistoryRestored(); + } else { + Toast.makeText(purchaseActivity, R.string.could_not_restore_purchase, Toast.LENGTH_SHORT) + .show(); + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/SearchActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SearchActivity.java new file mode 100644 index 00000000..961b5b64 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SearchActivity.java @@ -0,0 +1,267 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.os.Bundle; +import android.speech.RecognizerIntent; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CoordinatorLayout; +import android.support.transition.TransitionManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SearchView.OnQueryTextListener; +import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.MenuItem; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.TextView.BufferType; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Locale; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.mvp.contract.SearchContract; +import code.name.monkey.retromusic.mvp.presenter.SearchPresenter; +import code.name.monkey.retromusic.ui.activities.base.AbsMusicServiceActivity; +import code.name.monkey.retromusic.ui.adapter.SearchAdapter; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.util.ViewUtil; + +public class SearchActivity extends AbsMusicServiceActivity implements OnQueryTextListener, + SearchContract.SearchView, TextWatcher { + + public static final String TAG = SearchActivity.class.getSimpleName(); + public static final String QUERY = "query"; + private static final int REQ_CODE_SPEECH_INPUT = 9002; + @BindView(R.id.voice_search) + View micIcon; + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(android.R.id.empty) + TextView empty; + @BindView(R.id.search_view) + EditText searchView; + @BindView(R.id.root) + CoordinatorLayout container; + @BindView(R.id.appbar) + AppBarLayout appbar; + @BindView(R.id.status_bar) + View statusBar; + private SearchPresenter searchPresenter; + private SearchAdapter adapter; + private String query; + + private boolean isMicSearch = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setDrawUnderStatusbar(true); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_search); + + ButterKnife.bind(this); + + ViewUtil.setStatusBarHeight(this, statusBar); + + searchPresenter = new SearchPresenter(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + setupRecyclerview(); + setUpToolBar(); + setupSearchView(); + + if (savedInstanceState != null) { + query = savedInstanceState.getString(QUERY); + searchPresenter.search(query); + } + + if (getIntent().getBooleanExtra("mic_search", false)) { + startMicSearch(); + isMicSearch = true; + } + } + + private void setupRecyclerview() { + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + adapter = new SearchAdapter(this, Collections.emptyList()); + adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + empty.setVisibility(adapter.getItemCount() < 1 ? View.VISIBLE : View.GONE); + } + }); + recyclerView.setAdapter(adapter); + } + + private void setupSearchView() { + SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); + if (searchManager != null) { + searchView.addTextChangedListener(this); + } + } + + @Override + protected void onResume() { + super.onResume(); + //Log.i(TAG, "onResume: " + query); + searchPresenter.subscribe(); + searchPresenter.search(query); + } + + @Override + public void onDestroy() { + super.onDestroy(); + searchPresenter.unsubscribe(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(QUERY, query); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + searchPresenter.search(savedInstanceState.getString(QUERY, "")); + } + + private void setUpToolBar() { + int primaryColor = ThemeStore.primaryColor(this); + toolbar.setBackgroundColor(primaryColor); + appbar.setBackgroundColor(primaryColor); + setSupportActionBar(toolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + } + return super.onOptionsItemSelected(item); + } + + private void search(@NonNull String query) { + this.query = query.trim(); + TransitionManager.beginDelayedTransition(toolbar); + micIcon.setVisibility(query.length() > 0 ? View.GONE : View.VISIBLE); + searchPresenter.search(query); + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + searchPresenter.search(query); + } + + @Override + public boolean onQueryTextSubmit(String query) { + hideSoftKeyboard(); + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + search(newText); + return false; + } + + private void hideSoftKeyboard() { + RetroUtil.hideSoftKeyboard(SearchActivity.this); + if (searchView != null) { + searchView.clearFocus(); + } + } + + @Override + public void loading() { + + } + + @Override + public void showEmptyView() { + adapter.swapDataSet(new ArrayList<>()); + } + + @Override + public void completed() { + + } + + @Override + public void showData(ArrayList list) { + adapter.swapDataSet(list); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQ_CODE_SPEECH_INPUT: { + if (resultCode == RESULT_OK && null != data) { + + ArrayList result = data + .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); + query = result.get(0); + searchView.setText(query, BufferType.EDITABLE); + searchPresenter.search(query); + } + break; + } + } + } + + @OnClick(R.id.voice_search) + void searchImageView() { + startMicSearch(); + } + + private void startMicSearch() { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent + .putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault()); + intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.speech_prompt)); + try { + startActivityForResult(intent, REQ_CODE_SPEECH_INPUT); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + Toast.makeText(this, getString(R.string.speech_not_supported), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence newText, int start, int before, int count) { + search(newText.toString()); + } + + @Override + public void afterTextChanged(Editable s) { + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.java new file mode 100755 index 00000000..4bdd7df1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SettingsActivity.java @@ -0,0 +1,150 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.transition.TransitionManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.widget.FrameLayout; + +import com.afollestad.materialdialogs.color.ColorChooserDialog; + +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager; +import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; +import code.name.monkey.retromusic.ui.fragments.settings.MainSettingsFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; + +public class SettingsActivity extends AbsBaseActivity implements ColorChooserDialog.ColorCallback { + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.app_bar) + AppBarLayout appBarLayout; + @BindView(R.id.detail_content_frame) + @Nullable + FrameLayout detailsFrame; + + private FragmentManager fragmentManager = getSupportFragmentManager(); + + @Override + public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) { + switch (dialog.getTitle()) { + case R.string.primary_color: + int theme = ColorUtil.isColorLight(selectedColor) ? + PreferenceUtil.getThemeResFromPrefValue("light") : + PreferenceUtil.getThemeResFromPrefValue("dark"); + + ThemeStore.editTheme(this).activityTheme(theme).primaryColor(selectedColor).commit(); + break; + case R.string.accent_color: + ThemeStore.editTheme(this).accentColor(selectedColor).commit(); + break; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + new DynamicShortcutManager(this).updateDynamicShortcuts(); + } + recreate(); + } + + @Override + public void onColorChooserDismissed(@NonNull ColorChooserDialog dialog) { + + } + + @Override + protected void onCreate(@Nullable Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.activity_settings); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + setupToolbar(); + + if (bundle == null) { + fragmentManager.beginTransaction().replace(R.id.content_frame, new MainSettingsFragment()).commit(); + } else { + restoreFragment(); + } + } + + private void setupToolbar() { + appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this)); + toolbar.setBackgroundColor(ThemeStore.primaryColor(this)); + toolbar.setNavigationOnClickListener(v -> onBackPressed()); + setTitle(R.string.action_settings); + setSupportActionBar(toolbar); + } + + private void restoreFragment() { + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + if (fragmentManager.getBackStackEntryCount() > 0) { + appBarLayout.setExpanded(false, true); + } else { + appBarLayout.setExpanded(true, true); + } + setupToolbar(); + } + + public void setupFragment(Fragment fragment) { + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction() + .setCustomAnimations(R.animator.slide_up, 0, 0, R.animator.slide_down); + + if (detailsFrame == null) { + fragmentTransaction.replace(R.id.content_frame, fragment, fragment.getTag()); + fragmentTransaction.addToBackStack(null); + fragmentTransaction.commit(); + } else { + fragmentTransaction.replace(R.id.detail_content_frame, fragment, fragment.getTag()); + fragmentTransaction.commit(); + } + + fragmentManager.addOnBackStackChangedListener(() -> { + if (fragmentManager.getBackStackEntryCount() > 0) { + appBarLayout.setExpanded(false, true); + } else { + appBarLayout.setExpanded(true, true); + } + setupToolbar(); + }); + } + + + @Override + public void onBackPressed() { + if (fragmentManager.getBackStackEntryCount() == 0) { + super.onBackPressed(); + } else { + fragmentManager.popBackStack(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + public void addAppbarLayoutElevation(float v) { + TransitionManager.beginDelayedTransition(appBarLayout); + appBarLayout.setElevation(v); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/SupportDevelopmentActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SupportDevelopmentActivity.java new file mode 100644 index 00000000..604a0998 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/SupportDevelopmentActivity.java @@ -0,0 +1,318 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.content.Intent; +import android.graphics.Paint; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.afollestad.materialdialogs.internal.MDTintHelper; +import com.anjlab.android.iab.v3.BillingProcessor; +import com.anjlab.android.iab.v3.SkuDetails; +import com.anjlab.android.iab.v3.TransactionDetails; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.retromusic.BuildConfig; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.views.IconImageView; + +import static code.name.monkey.retromusic.Constants.PAYPAL_ME_URL; + +/** + * @author Hemanth S (h4h13). + */ + +public class SupportDevelopmentActivity extends AbsBaseActivity implements BillingProcessor.IBillingHandler { + public static final String TAG = SupportDevelopmentActivity.class.getSimpleName(); + private static final int DONATION_PRODUCT_IDS = R.array.donation_ids; + @BindView(R.id.progress) + ProgressBar mProgressBar; + @BindView(R.id.progress_container) + View mProgressContainer; + @BindView(R.id.list) + RecyclerView mListView; + @BindView(R.id.toolbar) + Toolbar mToolbar; + @BindView(R.id.app_bar) + AppBarLayout mAppBarLayout; + @BindView(R.id.root) + ViewGroup mViewGroup; + private BillingProcessor mBillingProcessor; + private AsyncTask skuDetailsLoadAsyncTask; + + private static List getDetails() { + List skuDetails = new ArrayList<>(); + JSONObject jsonObject = new JSONObject(); + try { + for (int i = 0; i < 6; i++) { + jsonObject.put("songTitle", "Coffee"); + jsonObject.put("price", "$100"); + jsonObject.put("description", "" + i); + skuDetails.add(new SkuDetails(jsonObject)); + } + + } catch (JSONException e) { + e.printStackTrace(); + } + + return skuDetails; + } + + private void donate(int i) { + final String[] ids = getResources().getStringArray(DONATION_PRODUCT_IDS); + mBillingProcessor.purchase(this, ids[i]); + } + + @OnClick(R.id.donate) + void donate() { + RetroUtil.openUrl(this, PAYPAL_ME_URL); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_donation); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + int primaryColor = ThemeStore.primaryColor(this); + mAppBarLayout.setBackgroundColor(primaryColor); + mToolbar.setBackgroundColor(primaryColor); + setSupportActionBar(mToolbar); + //noinspection ConstantConditions + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + mToolbar.setNavigationOnClickListener(view -> onBackPressed()); + + mBillingProcessor + = new BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSE_KEY, this); + MDTintHelper.setTint(mProgressBar, ThemeStore.accentColor(this)); + + ((TextView) findViewById(R.id.donation)).setTextColor(ThemeStore.accentColor(this)); + } + + @Override + public void onBillingInitialized() { + loadSkuDetails(); + } + + private void loadSkuDetails() { + if (skuDetailsLoadAsyncTask != null) { + skuDetailsLoadAsyncTask.cancel(false); + } + skuDetailsLoadAsyncTask = new SkuDetailsLoadAsyncTask(this).execute(); + + } + + @Override + public void onProductPurchased(@NonNull String productId, TransactionDetails details) { + //loadSkuDetails(); + Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onBillingError(int errorCode, Throwable error) { + Log.e(TAG, "Billing error: code = " + errorCode, error); + } + + @Override + public void onPurchaseHistoryRestored() { + //loadSkuDetails(); + Toast.makeText(this, R.string.restored_previous_purchases, Toast.LENGTH_SHORT).show(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (!mBillingProcessor.handleActivityResult(requestCode, resultCode, data)) { + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onDestroy() { + if (mBillingProcessor != null) { + mBillingProcessor.release(); + } + if (skuDetailsLoadAsyncTask != null) { + skuDetailsLoadAsyncTask.cancel(true); + } + super.onDestroy(); + } + + private static class SkuDetailsLoadAsyncTask extends AsyncTask> { + private final WeakReference donationDialogWeakReference; + + public SkuDetailsLoadAsyncTask(SupportDevelopmentActivity donationsDialog) { + this.donationDialogWeakReference = new WeakReference<>(donationsDialog); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + SupportDevelopmentActivity dialog = donationDialogWeakReference.get(); + if (dialog == null) return; + + dialog.mProgressContainer.setVisibility(View.VISIBLE); + dialog.mListView.setVisibility(View.GONE); + } + + @Override + protected List doInBackground(Void... params) { + SupportDevelopmentActivity dialog = donationDialogWeakReference.get(); + if (dialog != null) { + final String[] ids = dialog.getResources().getStringArray(DONATION_PRODUCT_IDS); + return dialog.mBillingProcessor.getPurchaseListingDetails(new ArrayList<>(Arrays.asList(ids))); + } + cancel(false); + return null; + } + + @Override + protected void onPostExecute(List skuDetails) { + super.onPostExecute(skuDetails); + SupportDevelopmentActivity dialog = donationDialogWeakReference.get(); + if (dialog == null) return; + + if (skuDetails == null || skuDetails.isEmpty()) { + //Toast.makeText(dialog, "Error loading items", Toast.LENGTH_SHORT).show(); + //dialog.finish(); + dialog.mProgressContainer.setVisibility(View.GONE); + return; + } + + //noinspection ConstantConditions + dialog.mProgressContainer.setVisibility(View.GONE); + dialog.mListView.setItemAnimator(new DefaultItemAnimator()); + dialog.mListView.setLayoutManager(new GridLayoutManager(dialog, 2)); + dialog.mListView.setAdapter(new SkuDetailsAdapter(dialog, skuDetails)); + dialog.mListView.setVisibility(View.VISIBLE); + } + + + } + + static class SkuDetailsAdapter extends RecyclerView.Adapter { + @LayoutRes + private static int LAYOUT_RES_ID = R.layout.item_donation_option; + + SupportDevelopmentActivity donationsDialog; + List skuDetailsList = new ArrayList<>(); + + public SkuDetailsAdapter(@NonNull SupportDevelopmentActivity donationsDialog, @NonNull List objects) { + this.donationsDialog = donationsDialog; + skuDetailsList = objects; + } + + private static void strikeThrough(TextView textView, boolean strikeThrough) { + textView.setPaintFlags(strikeThrough ? textView.getPaintFlags() | + Paint.STRIKE_THRU_TEXT_FLAG : textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); + } + + private int getIcon(int position) { + switch (position) { + case 0: + return R.drawable.ic_cookie_white_24dp; + case 1: + return R.drawable.ic_take_away_white_24dp; + case 2: + return R.drawable.ic_take_away_coffe_white_24dp; + case 3: + return R.drawable.ic_beer_white_24dp; + case 4: + return R.drawable.ic_fast_food_meal_white_24dp; + case 5: + return R.drawable.ic_popcorn_white_24dp; + case 6: + return R.drawable.ic_card_giftcard_white_24dp; + default: + return R.drawable.ic_card_giftcard_white_24dp; + } + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + return new ViewHolder(LayoutInflater.from(donationsDialog) + .inflate(LAYOUT_RES_ID, viewGroup, false)); + } + + @Override + public void onBindViewHolder(ViewHolder viewHolder, int i) { + SkuDetails skuDetails = skuDetailsList.get(i); + if (skuDetails != null) { + viewHolder.title.setText(skuDetails.title.replace("(Retro Music Player)", "").trim()); + viewHolder.text.setText(skuDetails.description); + viewHolder.text.setVisibility(View.GONE); + viewHolder.price.setText(skuDetails.priceText); + viewHolder.image.setImageResource(getIcon(i)); + + final boolean purchased = donationsDialog.mBillingProcessor.isPurchased(skuDetails.productId); + int titleTextColor = purchased ? ATHUtil.resolveColor(donationsDialog, android.R.attr.textColorHint) : ThemeStore.textColorPrimary(donationsDialog); + int contentTextColor = purchased ? titleTextColor : ThemeStore.textColorSecondary(donationsDialog); + + //noinspection ResourceAsColor + viewHolder.title.setTextColor(titleTextColor); + viewHolder.text.setTextColor(contentTextColor); + viewHolder.price.setTextColor(titleTextColor); + + strikeThrough(viewHolder.title, purchased); + strikeThrough(viewHolder.text, purchased); + strikeThrough(viewHolder.price, purchased); + + viewHolder.itemView.setOnTouchListener((v, event) -> purchased); + viewHolder.itemView.setOnClickListener(v -> donationsDialog.donate(i)); + } + } + + @Override + public int getItemCount() { + return skuDetailsList.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.title) + TextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.price) + TextView price; + @BindView(R.id.image) + IconImageView image; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/UserInfoActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/UserInfoActivity.java new file mode 100644 index 00000000..303d1cf9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/UserInfoActivity.java @@ -0,0 +1,33 @@ +package code.name.monkey.retromusic.ui.activities; + +import android.os.Bundle; + +import butterknife.ButterKnife; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; +import code.name.monkey.retromusic.ui.fragments.intro.NameFragment; + +public class UserInfoActivity extends AbsBaseActivity { + private static final String TAG = "UserInfoActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setDrawUnderStatusbar(true); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_user_info); + + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + setTaskDescriptionColorAuto(); + setLightNavigationBar(true); + + if (savedInstanceState == null) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragment_container, new NameFragment(), TAG) + .commit(); + } + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.java new file mode 100644 index 00000000..3c16824e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsBaseActivity.java @@ -0,0 +1,156 @@ +package code.name.monkey.retromusic.ui.activities.base; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; +import android.view.KeyEvent; +import android.view.View; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; + + +public abstract class AbsBaseActivity extends AbsThemeActivity { + public static final int PERMISSION_REQUEST = 100; + private boolean hadPermissions; + private String[] permissions; + private String permissionDeniedMessage; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setVolumeControlStream(AudioManager.STREAM_MUSIC); + + permissions = getPermissionsToRequest(); + hadPermissions = hasPermissions(); + + setPermissionDeniedMessage(null); + + + } + + @Override + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + if (!hasPermissions()) { + requestPermissions(); + } + } + + @Override + protected void onResume() { + super.onResume(); + final boolean hasPermissions = hasPermissions(); + if (hasPermissions != hadPermissions) { + hadPermissions = hasPermissions; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + onHasPermissionsChanged(hasPermissions); + } + } + + } + + protected void onHasPermissionsChanged(boolean hasPermissions) { + // implemented by sub classes + } + + @Override + public boolean dispatchKeyEvent(@NonNull KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && event.getAction() == KeyEvent.ACTION_UP) { + showOverflowMenu(); + return true; + } + return super.dispatchKeyEvent(event); + } + + protected void showOverflowMenu() { + + } + + @Override + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase)); + } + + @Nullable + protected String[] getPermissionsToRequest() { + return null; + } + + protected View getSnackBarContainer() { + return getWindow().getDecorView(); + } + + private String getPermissionDeniedMessage() { + return permissionDeniedMessage == null ? getString(R.string.permissions_denied) : permissionDeniedMessage; + } + + protected void setPermissionDeniedMessage(String message) { + permissionDeniedMessage = message; + } + + protected void requestPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) { + requestPermissions(permissions, PERMISSION_REQUEST); + } + } + + protected boolean hasPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) { + for (String permission : permissions) { + if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + } + return true; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == PERMISSION_REQUEST) { + for (int grantResult : grantResults) { + if (grantResult != PackageManager.PERMISSION_GRANTED) { + if (ActivityCompat.shouldShowRequestPermissionRationale(AbsBaseActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + //User has deny from permission dialog + Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(), + Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.action_grant, view -> requestPermissions()) + .setActionTextColor(ThemeStore.accentColor(this)) + .show(); + } else { + // User has deny permission and checked never show permission dialog so you can redirect to Application settings page + Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(), + Snackbar.LENGTH_INDEFINITE) + .setAction(R.string.action_settings, view -> { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", AbsBaseActivity.this.getPackageName(), null); + intent.setData(uri); + startActivity(intent); + }) + .setActionTextColor(ThemeStore.accentColor(this)) + .show(); + } + return; + } + } + hadPermissions = true; + onHasPermissionsChanged(true); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsMusicServiceActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsMusicServiceActivity.java new file mode 100644 index 00000000..4d0f28a6 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsMusicServiceActivity.java @@ -0,0 +1,222 @@ +package code.name.monkey.retromusic.ui.activities.base; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +import code.name.monkey.retromusic.interfaces.MusicServiceEventListener; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; + +import static code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED; +import static code.name.monkey.retromusic.Constants.META_CHANGED; +import static code.name.monkey.retromusic.Constants.PLAY_STATE_CHANGED; +import static code.name.monkey.retromusic.Constants.QUEUE_CHANGED; +import static code.name.monkey.retromusic.Constants.REPEAT_MODE_CHANGED; +import static code.name.monkey.retromusic.Constants.SHUFFLE_MODE_CHANGED; + + +public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements MusicServiceEventListener { + public static final String TAG = AbsMusicServiceActivity.class.getSimpleName(); + + private final ArrayList mMusicServiceEventListeners = new ArrayList<>(); + + private MusicPlayerRemote.ServiceToken serviceToken; + private MusicStateReceiver musicStateReceiver; + private boolean receiverRegistered; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + serviceToken = MusicPlayerRemote.bindToService(this, new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + AbsMusicServiceActivity.this.onServiceConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + AbsMusicServiceActivity.this.onServiceDisconnected(); + } + }); + + setPermissionDeniedMessage(getString(R.string.permission_external_storage_denied)); + } + + @Override + public void onDestroy() { + super.onDestroy(); + MusicPlayerRemote.unbindFromService(serviceToken); + if (receiverRegistered) { + unregisterReceiver(musicStateReceiver); + receiverRegistered = false; + } + } + + public void addMusicServiceEventListener(final MusicServiceEventListener listener) { + if (listener != null) { + mMusicServiceEventListeners.add(listener); + } + } + + public void removeMusicServiceEventListener(final MusicServiceEventListener listener) { + if (listener != null) { + mMusicServiceEventListeners.remove(listener); + } + } + + @Override + public void onServiceConnected() { + if (!receiverRegistered) { + musicStateReceiver = new MusicStateReceiver(this); + + final IntentFilter filter = new IntentFilter(); + filter.addAction(PLAY_STATE_CHANGED); + filter.addAction(SHUFFLE_MODE_CHANGED); + filter.addAction(REPEAT_MODE_CHANGED); + filter.addAction(META_CHANGED); + filter.addAction(QUEUE_CHANGED); + filter.addAction(MEDIA_STORE_CHANGED); + + registerReceiver(musicStateReceiver, filter); + + receiverRegistered = true; + } + + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onServiceConnected(); + } + } + } + + @Override + public void onServiceDisconnected() { + if (receiverRegistered) { + unregisterReceiver(musicStateReceiver); + receiverRegistered = false; + } + + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onServiceDisconnected(); + } + } + } + + @Override + public void onPlayingMetaChanged() { + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onPlayingMetaChanged(); + } + } + } + + @Override + public void onQueueChanged() { + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onQueueChanged(); + } + } + } + + @Override + public void onPlayStateChanged() { + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onPlayStateChanged(); + } + } + } + + @Override + public void onMediaStoreChanged() { + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onMediaStoreChanged(); + } + } + } + + @Override + public void onRepeatModeChanged() { + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onRepeatModeChanged(); + } + } + } + + @Override + public void onShuffleModeChanged() { + for (MusicServiceEventListener listener : mMusicServiceEventListeners) { + if (listener != null) { + listener.onShuffleModeChanged(); + } + } + } + + @Override + protected void onHasPermissionsChanged(boolean hasPermissions) { + super.onHasPermissionsChanged(hasPermissions); + Intent intent = new Intent(MEDIA_STORE_CHANGED); + intent.putExtra("from_permissions_changed", true); // just in case we need to know this at some point + sendBroadcast(intent); + } + + @Nullable + @Override + protected String[] getPermissionsToRequest() { + return new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } + + private static final class MusicStateReceiver extends BroadcastReceiver { + + private final WeakReference reference; + + public MusicStateReceiver(final AbsMusicServiceActivity activity) { + reference = new WeakReference<>(activity); + } + + @Override + public void onReceive(final Context context, @NonNull final Intent intent) { + final String action = intent.getAction(); + AbsMusicServiceActivity activity = reference.get(); + if (activity != null && action != null) { + switch (action) { + case META_CHANGED: + activity.onPlayingMetaChanged(); + break; + case QUEUE_CHANGED: + activity.onQueueChanged(); + break; + case PLAY_STATE_CHANGED: + activity.onPlayStateChanged(); + break; + case REPEAT_MODE_CHANGED: + activity.onRepeatModeChanged(); + break; + case SHUFFLE_MODE_CHANGED: + activity.onShuffleModeChanged(); + break; + case MEDIA_STORE_CHANGED: + activity.onMediaStoreChanged(); + break; + } + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsSlidingMusicPanelActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsSlidingMusicPanelActivity.java new file mode 100644 index 00000000..402b7fc1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsSlidingMusicPanelActivity.java @@ -0,0 +1,434 @@ +package code.name.monkey.retromusic.ui.activities.base; + +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.FloatRange; +import android.support.annotation.LayoutRes; +import android.support.v4.app.Fragment; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.PathInterpolator; + +import com.sothree.slidinguppanel.SlidingUpPanelLayout; +import com.sothree.slidinguppanel.SlidingUpPanelLayout.PanelState; + +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.ui.fragments.MiniPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.NowPlayingScreen; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.adaptive.AdaptiveFragment; +import code.name.monkey.retromusic.ui.fragments.player.blur.BlurPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.card.CardFragment; +import code.name.monkey.retromusic.ui.fragments.player.cardblur.CardBlurFragment; +import code.name.monkey.retromusic.ui.fragments.player.color.ColorFragment; +import code.name.monkey.retromusic.ui.fragments.player.flat.FlatPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.full.FullPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.hmm.HmmPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.material.MaterialFragment; +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.plain.PlainPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.simple.SimplePlayerFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.ViewUtil; +import code.name.monkey.retromusic.views.BottomNavigationViewEx; + +public abstract class AbsSlidingMusicPanelActivity extends AbsMusicServiceActivity implements + SlidingUpPanelLayout.PanelSlideListener, + PlayerFragment.Callbacks { + + public static final String TAG = AbsSlidingMusicPanelActivity.class.getSimpleName(); + + @BindView(R.id.bottom_navigation) + BottomNavigationViewEx bottomNavigationView; + @BindView(R.id.sliding_layout) + SlidingUpPanelLayout slidingUpPanelLayout; + + private int navigationbarColor; + private int taskColor; + private boolean lightStatusBar; + private boolean lightNavigationBar; + private NowPlayingScreen currentNowPlayingScreen; + private AbsPlayerFragment playerFragment; + private MiniPlayerFragment miniPlayerFragment; + private ValueAnimator navigationBarColorAnimator; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(createContentView()); + ButterKnife.bind(this); + choosFragmentForTheme(); + //noinspection ConstantConditions + miniPlayerFragment.getView().setOnClickListener(v -> expandPanel()); + slidingUpPanelLayout.getViewTreeObserver() + .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + slidingUpPanelLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + + if (getPanelState() == PanelState.EXPANDED) { + onPanelSlide(slidingUpPanelLayout, 1); + onPanelExpanded(slidingUpPanelLayout); + } else if (getPanelState() == PanelState.COLLAPSED) { + onPanelCollapsed(slidingUpPanelLayout); + } else { + playerFragment.onHide(); + } + } + }); + + setupBottomView(); + slidingUpPanelLayout.addPanelSlideListener(this); + + } + + private void choosFragmentForTheme() { + currentNowPlayingScreen = PreferenceUtil.getInstance(this).getNowPlayingScreen(); + + Fragment fragment; // must implement AbsPlayerFragment + switch (currentNowPlayingScreen) { + case MATERIAL: + fragment = new MaterialFragment(); + break; + case BLUR: + fragment = new BlurPlayerFragment(); + break; + case FLAT: + fragment = new FlatPlayerFragment(); + break; + case PLAIN: + fragment = new PlainPlayerFragment(); + break; + case FULL: + fragment = new FullPlayerFragment(); + break; + case COLOR: + fragment = new ColorFragment(); + break; + case CARD: + fragment = new CardFragment(); + break; + case SIMPLE: + fragment = new SimplePlayerFragment(); + break; + case TINY: + fragment = new HmmPlayerFragment(); + break; + case BLUR_CARD: + fragment = new CardBlurFragment(); + break; + case ADAPTIVE: + fragment = new AdaptiveFragment(); + break; + + case NORMAL: + default: + fragment = new PlayerFragment(); + break; + } + getSupportFragmentManager().beginTransaction().replace(R.id.player_fragment_container, fragment) + .commit(); + getSupportFragmentManager().executePendingTransactions(); + + playerFragment = (AbsPlayerFragment) getSupportFragmentManager() + .findFragmentById(R.id.player_fragment_container); + miniPlayerFragment = (MiniPlayerFragment) getSupportFragmentManager() + .findFragmentById(R.id.mini_player_fragment); + } + + private void setupBottomView() { + bottomNavigationView.setSelectedItemId(PreferenceUtil.getInstance(this).getLastPage()); + bottomNavigationView.setBackgroundColor(ThemeStore.primaryColor(this)); + bottomNavigationView.enableAnimation(false); + bottomNavigationView.enableItemShiftingMode(false); + bottomNavigationView.enableShiftingMode(false); + bottomNavigationView.setTextSize(10f); + bottomNavigationView.setTextVisibility(PreferenceUtil.getInstance(this).tabTitles()); + } + + @Override + protected void onResume() { + super.onResume(); + if (currentNowPlayingScreen != PreferenceUtil.getInstance(this).getNowPlayingScreen()) { + postRecreate(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (navigationBarColorAnimator != null) { + navigationBarColorAnimator.cancel(); // just in case + } + } + + + protected abstract View createContentView(); + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + if (!MusicPlayerRemote.getPlayingQueue().isEmpty()) { + slidingUpPanelLayout.getViewTreeObserver() + .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + slidingUpPanelLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + hideBottomBar(false); + } + }); + }// don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout + } + + @Override + public void onQueueChanged() { + super.onQueueChanged(); + hideBottomBar(MusicPlayerRemote.getPlayingQueue().isEmpty()); + } + + @Override + public void onPanelSlide(View panel, @FloatRange(from = 0, to = 1) float slideOffset) { + bottomNavigationView.setTranslationY(slideOffset * 400); + setMiniPlayerAlphaProgress(slideOffset); + //findViewById(R.id.player_fragment_container).setAlpha(slideOffset); + } + + @Override + public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState) { + switch (newState) { + case COLLAPSED: + onPanelCollapsed(panel); + break; + case EXPANDED: + onPanelExpanded(panel); + break; + case ANCHORED: + collapsePanel(); // this fixes a bug where the panel would get stuck for some reason + break; + } + } + + public void onPanelCollapsed(View panel) { + // restore values + super.setLightStatusbar(lightStatusBar); + super.setTaskDescriptionColor(taskColor); + super.setNavigationbarColor(navigationbarColor); + super.setLightNavigationBar(lightNavigationBar); + + playerFragment.setMenuVisibility(false); + playerFragment.setUserVisibleHint(false); + playerFragment.onHide(); + } + + public void onPanelExpanded(View panel) { + // setting fragments values + int playerFragmentColor = playerFragment.getPaletteColor(); + super.setTaskDescriptionColor(playerFragmentColor); + + if (currentNowPlayingScreen == NowPlayingScreen.COLOR) { + super.setNavigationbarColor(playerFragmentColor); + } else { + super.setNavigationbarColor(ThemeStore.primaryColor(this)); + } + + setLightStatusBar(); + + playerFragment.setMenuVisibility(true); + playerFragment.setUserVisibleHint(true); + playerFragment.onShow(); + } + + private void setLightStatusBar() { + super.setLightStatusbar(!PreferenceUtil.getInstance(this).getAdaptiveColor() && + ColorUtil.isColorLight(ThemeStore.primaryColor(this)) && ( + currentNowPlayingScreen == NowPlayingScreen.FLAT + || currentNowPlayingScreen == NowPlayingScreen.PLAIN + || currentNowPlayingScreen == NowPlayingScreen.SIMPLE + || currentNowPlayingScreen == NowPlayingScreen.NORMAL + || currentNowPlayingScreen == NowPlayingScreen.ADAPTIVE) + || currentNowPlayingScreen == NowPlayingScreen.TINY); + } + + @Override + public void setLightStatusbar(boolean enabled) { + lightStatusBar = enabled; + if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { + super.setLightStatusbar(enabled); + } + } + + @Override + public void setLightNavigationBar(boolean enabled) { + lightNavigationBar = enabled; + /*if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { + super.setLightNavigationBar(enabled); + }*/ + } + + @Override + public void setTaskDescriptionColor(@ColorInt int color) { + taskColor = color; + if (getPanelState() == null || getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { + super.setTaskDescriptionColor(color); + } + } + + @Override + public void setNavigationbarColor(int color) { + navigationbarColor = color; + if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { + if (navigationBarColorAnimator != null) { + navigationBarColorAnimator.cancel(); + } + super.setNavigationbarColor(color); + } + } + + @Override + public void onPaletteColorChanged() { + int playerFragmentColor = playerFragment.getPaletteColor(); + + if (getPanelState() == PanelState.EXPANDED) { + super.setTaskDescriptionColor(playerFragmentColor); + if (currentNowPlayingScreen == NowPlayingScreen.COLOR) { + super.setNavigationbarColor(playerFragmentColor); + } + } + } + + private void setMiniPlayerAlphaProgress(@FloatRange(from = 0, to = 1) float progress) { + if (miniPlayerFragment == null) { + return; + } + float alpha = 1 - progress; + miniPlayerFragment.getView().setAlpha(alpha); + // necessary to make the views below clickable + miniPlayerFragment.getView().setVisibility(alpha == 0 ? View.GONE : View.VISIBLE); + + } + + public void hideBottomBar(final boolean hide) { + + int heightOfBar = + getResources().getDimensionPixelSize(R.dimen.mini_player_height); + int heightOfBarWithTabs = + getResources().getDimensionPixelSize(R.dimen.mini_player_height_expanded); + + if (hide) { + slidingUpPanelLayout.setPanelHeight(0); + collapsePanel(); + } else { + if (!MusicPlayerRemote.getPlayingQueue().isEmpty()) { + slidingUpPanelLayout.setPanelHeight(bottomNavigationView.getVisibility() == View.VISIBLE ? + heightOfBarWithTabs : heightOfBar); + } + } + } + + public void setBottomBarVisibility(int gone) { + if (bottomNavigationView != null) { + //TransitionManager.beginDelayedTransition(bottomNavigationView); + bottomNavigationView.setVisibility(gone); + hideBottomBar(false); + } + } + + protected View wrapSlidingMusicPanel(@LayoutRes int resId) { + @SuppressLint("InflateParams") + View slidingMusicPanelLayout = getLayoutInflater() + .inflate(R.layout.sliding_music_panel_layout, null); + ViewGroup contentContainer = slidingMusicPanelLayout.findViewById(R.id.content_container); + getLayoutInflater().inflate(resId, contentContainer); + return slidingMusicPanelLayout; + } + + @Override + public void onBackPressed() { + if (!handleBackPress()) { + super.onBackPressed(); + } + } + + public boolean handleBackPress() { + if (slidingUpPanelLayout.getPanelHeight() != 0 && playerFragment.onBackPressed()) { + return true; + } + if (getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) { + collapsePanel(); + return true; + } + return false; + } + + private void animateNavigationBarColor(int color) { + if (navigationBarColorAnimator != null) { + navigationBarColorAnimator.cancel(); + } + navigationBarColorAnimator = ValueAnimator.ofArgb(getWindow().getNavigationBarColor(), color) + .setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME); + navigationBarColorAnimator.setInterpolator(new PathInterpolator(0.4f, 0f, 1f, 1f)); + navigationBarColorAnimator.addUpdateListener(animation -> { + int playerFragmentColorDark = ColorUtil.darkenColor((Integer) animation.getAnimatedValue()); + + bottomNavigationView.setBackgroundColor(playerFragmentColorDark); + miniPlayerFragment.setColor(playerFragmentColorDark); + AbsSlidingMusicPanelActivity.super.setNavigationbarColor(playerFragmentColorDark); + + View view = getWindow().getDecorView().getRootView(); + view.setBackgroundColor(playerFragmentColorDark); + + if (view.findViewById(R.id.toolbar) != null) { + view.findViewById(R.id.toolbar).setBackgroundColor(playerFragmentColorDark); + } + if (view.findViewById(R.id.appbar) != null) { + view.findViewById(R.id.appbar).setBackgroundColor(playerFragmentColorDark); + } + if (view.findViewById(R.id.status_bar) != null) { + view.findViewById(R.id.status_bar) + .setBackgroundColor(ColorUtil.darkenColor(playerFragmentColorDark)); + } + }); + navigationBarColorAnimator.start(); + } + + @Override + protected View getSnackBarContainer() { + return findViewById(R.id.content_container); + } + + public SlidingUpPanelLayout getSlidingUpPanelLayout() { + return slidingUpPanelLayout; + } + + public MiniPlayerFragment getMiniPlayerFragment() { + return miniPlayerFragment; + } + + public AbsPlayerFragment getPlayerFragment() { + return playerFragment; + } + + public BottomNavigationViewEx getBottomNavigationView() { + return bottomNavigationView; + } + + public SlidingUpPanelLayout.PanelState getPanelState() { + return slidingUpPanelLayout == null ? null : slidingUpPanelLayout.getPanelState(); + } + + public void collapsePanel() { + slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED); + } + + public void expandPanel() { + slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsThemeActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsThemeActivity.java new file mode 100644 index 00000000..1a4632ac --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/base/AbsThemeActivity.java @@ -0,0 +1,213 @@ +package code.name.monkey.retromusic.ui.activities.base; + +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.ColorInt; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; + +import code.name.monkey.appthemehelper.ATH; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.common.ATHToolbarActivity; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialDialogsUtil; +import code.name.monkey.appthemehelper.util.VersionUtils; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +public abstract class AbsThemeActivity extends ATHToolbarActivity implements Runnable { + + private Handler handler = new Handler(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(PreferenceUtil.getInstance(this).getGeneralTheme()); + hideStatusBar(); + super.onCreate(savedInstanceState); + MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this); + + changeBackgroundShape(); + setImmersiveFullscreen(); + registerSystemUiVisibility(); + toggleScreenOn(); + + } + + private void toggleScreenOn() { + if (PreferenceUtil.getInstance(this).isScreenOnEnabled()) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + hideStatusBar(); + handler.removeCallbacks(this); + handler.postDelayed(this, 300); + } else { + handler.removeCallbacks(this); + } + } + + public void hideStatusBar() { + hideStatusBar(PreferenceUtil.getInstance(this).getFullScreenMode()); + } + + private void hideStatusBar(boolean fullscreen) { + final View statusBar = getWindow().getDecorView().getRootView().findViewById(R.id.status_bar); + if (statusBar != null) { + statusBar.setVisibility(fullscreen ? View.GONE : View.VISIBLE); + } + } + + + private void changeBackgroundShape() { + if (PreferenceUtil.getInstance(this).isRoundCorners()) { + getWindow().setBackgroundDrawableResource(R.drawable.round_window); + } else { + getWindow().setBackgroundDrawableResource(R.drawable.square_window); + } + View decor = getWindow().getDecorView(); + GradientDrawable gradientDrawable = (GradientDrawable) decor.getBackground(); + gradientDrawable.setColor(ThemeStore.primaryColor(this)); + } + + protected void setDrawUnderStatusbar(boolean drawUnderStatusbar) { + if (VersionUtils.hasLollipop()) { + RetroUtil.setAllowDrawUnderStatusBar(getWindow()); + } else if (VersionUtils.hasKitKat()) { + RetroUtil.setStatusBarTranslucent(getWindow()); + } + } + + /** + * This will set the color of the view with the id "status_bar" on KitKat and Lollipop. On + * Lollipop if no such view is found it will set the statusbar color using the native method. + * + * @param color the new statusbar color (will be shifted down on Lollipop and above) + */ + public void setStatusbarColor(int color) { + if (VersionUtils.hasKitKat()) { + final View statusBar = getWindow().getDecorView().getRootView().findViewById(R.id.status_bar); + if (statusBar != null) { + if (VersionUtils.hasLollipop()) { + statusBar.setBackgroundColor(ColorUtil.darkenColor(color)); + setLightStatusbarAuto(color); + } else { + statusBar.setBackgroundColor(color); + } + } else if (Build.VERSION.SDK_INT >= 21) { + getWindow().setStatusBarColor(ColorUtil.darkenColor(color)); + setLightStatusbarAuto(color); + } + } + } + + public void setStatusbarColorAuto() { + // we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat + setStatusbarColor(ThemeStore.primaryColor(this)); + } + + public void setTaskDescriptionColor(@ColorInt int color) { + ATH.setTaskDescriptionColor(this, color); + } + + public void setTaskDescriptionColorAuto() { + setTaskDescriptionColor(ThemeStore.primaryColor(this)); + } + + public void setNavigationbarColor(int color) { + if (ThemeStore.coloredNavigationBar(this)) { + ATH.setNavigationbarColor(this, color); + } else { + ATH.setNavigationbarColor(this, Color.BLACK); + } + } + + public void setNavigationbarColorAuto() { + setNavigationbarColor(ThemeStore.navigationBarColor(this)); + } + + public void setLightStatusbar(boolean enabled) { + ATH.setLightStatusbar(this, enabled); + } + + public void setLightStatusbarAuto(int bgColor) { + setLightStatusbar(ColorUtil.isColorLight(bgColor)); + } + + public void setLightNavigationBar(boolean enabled) { + if (!ATHUtil.isWindowBackgroundDark(this) && ThemeStore.coloredNavigationBar(this)) { + ATH.setLightNavigationbar(this, enabled); + } + } + + private void registerSystemUiVisibility() { + final View decorView = getWindow().getDecorView(); + decorView.setOnSystemUiVisibilityChangeListener(visibility -> { + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { + setImmersiveFullscreen(); + } + }); + } + + private void unregisterSystemUiVisibility() { + final View decorView = getWindow().getDecorView(); + decorView.setOnSystemUiVisibilityChangeListener(null); + } + + public void setImmersiveFullscreen() { + int flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + if (PreferenceUtil.getInstance(this).getFullScreenMode()) { + getWindow().getDecorView().setSystemUiVisibility(flags); + } + } + + public void exitFullscreen() { + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + + @Override + public void run() { + setImmersiveFullscreen(); + } + + @Override + protected void onStop() { + handler.removeCallbacks(this); + super.onStop(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + unregisterSystemUiVisibility(); + exitFullscreen(); + } + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)) { + handler.removeCallbacks(this); + handler.postDelayed(this, 500); + } + return super.onKeyDown(keyCode, event); + + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/AbsTagEditorActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/AbsTagEditorActivity.java new file mode 100755 index 00000000..1a6580b3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/AbsTagEditorActivity.java @@ -0,0 +1,367 @@ +package code.name.monkey.retromusic.ui.activities.tageditor; + +import android.app.SearchManager; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.OvershootInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.afollestad.materialdialogs.MaterialDialog; + +import org.jaudiotagger.audio.AudioFile; +import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.tag.FieldKey; +import org.jaudiotagger.tag.images.Artwork; + +import java.io.File; +import java.util.List; +import java.util.Map; + +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.ui.activities.base.AbsBaseActivity; +import code.name.monkey.retromusic.util.RetroUtil; + +public abstract class AbsTagEditorActivity extends AbsBaseActivity { + + public static final String EXTRA_ID = "extra_id"; + public static final String EXTRA_PALETTE = "extra_palette"; + private static final String TAG = AbsTagEditorActivity.class.getSimpleName(); + private static final int REQUEST_CODE_SELECT_IMAGE = 1000; + @BindView(R.id.save_fab) + FloatingActionButton save; + @BindView(R.id.image) + ImageView image; + @BindView(R.id.image_container) + FrameLayout imageContainer; + CharSequence[] items; + private int id; + private int paletteColorPrimary; + private boolean isInNoImageMode; + private List songPaths; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getContentViewLayout()); + ButterKnife.bind(this); + + getIntentExtras(); + + songPaths = getSongPaths(); + if (songPaths.isEmpty()) { + finish(); + return; + } + + setUpViews(); + setTaskDescriptionColorAuto(); + } + + private void setUpViews() { + setUpScrollView(); + setUpFab(); + setUpImageView(); + } + + private void setUpScrollView() { + //observableScrollView.setScrollViewCallbacks(observableScrollViewCallbacks); + } + + private void setUpImageView() { + loadCurrentImage(); + items = new CharSequence[]{ + getString(R.string.download_from_last_fm), + getString(R.string.pick_from_local_storage), + getString(R.string.web_search), + getString(R.string.remove_cover) + }; + image.setOnClickListener(v -> getShow()); + } + + protected MaterialDialog getShow() { + return new MaterialDialog.Builder(AbsTagEditorActivity.this) + .title(R.string.update_image) + .items(items) + .itemsCallback((dialog, itemView, position, text) -> { + switch (position) { + case 0: + getImageFromLastFM(); + break; + case 1: + startImagePicker(); + break; + case 2: + searchImageOnWeb(); + break; + case 3: + deleteImage(); + break; + } + }).show(); + } + + private void startImagePicker() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + startActivityForResult( + Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), + REQUEST_CODE_SELECT_IMAGE); + } + + protected abstract void loadCurrentImage(); + + protected abstract void getImageFromLastFM(); + + protected abstract void searchImageOnWeb(); + + protected abstract void deleteImage(); + + private void setUpFab() { + save.setScaleX(0); + save.setScaleY(0); + save.setEnabled(false); + save.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + save(); + } + }); + + TintHelper.setTintAuto(save, ThemeStore.accentColor(this), true); + } + + protected abstract void save(); + + private void getIntentExtras() { + Bundle intentExtras = getIntent().getExtras(); + if (intentExtras != null) { + id = intentExtras.getInt(EXTRA_ID); + } + } + + protected abstract int getContentViewLayout(); + + @NonNull + protected abstract List getSongPaths(); + + protected void searchWebFor(String... keys) { + StringBuilder stringBuilder = new StringBuilder(); + for (String key : keys) { + stringBuilder.append(key); + stringBuilder.append(" "); + } + Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.putExtra(SearchManager.QUERY, stringBuilder.toString()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + startActivity(intent); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + super.onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + protected void setNoImageMode() { + isInNoImageMode = true; + imageContainer.setVisibility(View.GONE); + image.setVisibility(View.GONE); + image.setEnabled(false); + + setColors(getIntent().getIntExtra(EXTRA_PALETTE, ThemeStore.primaryColor(this))); + } + + protected void dataChanged() { + showFab(); + } + + private void showFab() { + save.animate() + .setDuration(500) + .setInterpolator(new OvershootInterpolator()) + .scaleX(1) + .scaleY(1) + .start(); + save.setEnabled(true); + } + + protected void setImageBitmap(@Nullable final Bitmap bitmap, int bgColor) { + if (bitmap == null) { + image.setImageResource(R.drawable.default_album_art); + } else { + image.setImageBitmap(bitmap); + } + setColors(bgColor); + } + + protected void setColors(int color) { + paletteColorPrimary = color; + } + + protected void writeValuesToFiles(@NonNull final Map fieldKeyValueMap, + @Nullable final ArtworkInfo artworkInfo) { + RetroUtil.hideSoftKeyboard(this); + + new WriteTagsAsyncTask(this) + .execute(new WriteTagsAsyncTask.LoadingInfo(getSongPaths(), fieldKeyValueMap, artworkInfo)); + } + + protected int getId() { + return id; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + @NonNull Intent imageReturnedIntent) { + super.onActivityResult(requestCode, resultCode, imageReturnedIntent); + switch (requestCode) { + case REQUEST_CODE_SELECT_IMAGE: + if (resultCode == RESULT_OK) { + Uri selectedImage = imageReturnedIntent.getData(); + loadImageFromFile(selectedImage); + } + break; + } + } + + protected abstract void loadImageFromFile(Uri selectedFile); + + @NonNull + private AudioFile getAudioFile(@NonNull String path) { + try { + return AudioFileIO.read(new File(path)); + } catch (Exception e) { + Log.e(TAG, "Could not read audio file " + path, e); + return new AudioFile(); + } + } + + @Nullable + String getAlbumArtist() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault() + .getFirst(FieldKey.ALBUM_ARTIST); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getSongTitle() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.TITLE); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getAlbumTitle() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ALBUM); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getArtistName() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ARTIST); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getAlbumArtistName() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault() + .getFirst(FieldKey.ALBUM_ARTIST); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getGenreName() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.GENRE); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getSongYear() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.YEAR); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getTrackNumber() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.TRACK); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected String getLyrics() { + try { + return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.LYRICS); + } catch (Exception ignored) { + return null; + } + } + + @Nullable + protected Bitmap getAlbumArt() { + try { + Artwork artworkTag = getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault() + .getFirstArtwork(); + if (artworkTag != null) { + byte[] artworkBinaryData = artworkTag.getBinaryData(); + return BitmapFactory.decodeByteArray(artworkBinaryData, 0, artworkBinaryData.length); + } + return null; + } catch (Exception ignored) { + return null; + } + } + + public static class ArtworkInfo { + + public final int albumId; + final Bitmap artwork; + + ArtworkInfo(int albumId, Bitmap artwork) { + this.albumId = albumId; + this.artwork = artwork; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/AlbumTagEditorActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/AlbumTagEditorActivity.java new file mode 100755 index 00000000..a83a17f4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/AlbumTagEditorActivity.java @@ -0,0 +1,281 @@ +package code.name.monkey.retromusic.ui.activities.tageditor; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import org.jaudiotagger.tag.FieldKey; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.loaders.AlbumLoader; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.rest.LastFMRestClient; +import code.name.monkey.retromusic.rest.model.LastFmAlbum; +import code.name.monkey.retromusic.util.ImageUtil; +import code.name.monkey.retromusic.util.LastFMUtil; +import code.name.monkey.retromusic.util.RetroColorUtil; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + + +public class AlbumTagEditorActivity extends AbsTagEditorActivity implements TextWatcher { + + public static final String TAG = AlbumTagEditorActivity.class.getSimpleName(); + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.app_bar) + AppBarLayout appBarLayout; + @BindView(R.id.title) + EditText albumTitle; + @BindView(R.id.album_artist) + EditText albumArtist; + @BindView(R.id.genre) + EditText genre; + @BindView(R.id.year) + EditText year; + @BindView(R.id.gradient_background) + View background; + private Bitmap albumArtBitmap; + private boolean deleteAlbumArt; + private LastFMRestClient lastFMRestClient; + + private void setupToolbar() { + //toolbar.setBackgroundColor(ThemeStore.primaryColor(this)); + toolbar.setNavigationOnClickListener(v -> onBackPressed()); + setTitle(R.string.action_tag_editor); + setSupportActionBar(toolbar); + } + + @OnClick(R.id.edit) + void edit() { + getShow(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ButterKnife.bind(this); + + lastFMRestClient = new LastFMRestClient(this); + + setUpViews(); + setupToolbar(); + } + + @Override + protected int getContentViewLayout() { + return R.layout.activity_album_tag_editor; + } + + private void setUpViews() { + fillViewsWithFileTags(); + albumTitle.addTextChangedListener(this); + albumArtist.addTextChangedListener(this); + genre.addTextChangedListener(this); + year.addTextChangedListener(this); + } + + + private void fillViewsWithFileTags() { + albumTitle.setText(getAlbumTitle()); + albumArtist.setText(getAlbumArtistName()); + genre.setText(getGenreName()); + year.setText(getSongYear()); + } + + @Override + protected void loadCurrentImage() { + Bitmap bitmap = getAlbumArt(); + setImageBitmap(bitmap, RetroColorUtil.getColor(RetroColorUtil.generatePalette(bitmap), + ATHUtil.resolveColor(this, R.attr.defaultFooterColor))); + deleteAlbumArt = false; + } + + @Override + protected void getImageFromLastFM() { + String albumTitleStr = albumTitle.getText().toString(); + String albumArtistNameStr = albumArtist.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; + } + + lastFMRestClient.getApiService() + .getAlbumInfo(albumTitleStr, albumArtistNameStr, null) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.computation()) + .subscribe(this::extractDetails); + } + + private void extractDetails(@NonNull LastFmAlbum lastFmAlbum) { + if (lastFmAlbum.getAlbum() != null) { + + String url = LastFMUtil.getLargestAlbumImageUrl(lastFmAlbum.getAlbum().getImage()); + + if (!TextUtils.isEmpty(url) && url.trim().length() > 0) { + Glide.with(this) + .load(url) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(this), BitmapPaletteWrapper.class) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .error(R.drawable.default_album_art) + .into(new SimpleTarget() { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + e.printStackTrace(); + Toast.makeText(AlbumTagEditorActivity.this, e.toString(), Toast.LENGTH_LONG).show(); + } + + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + albumArtBitmap = ImageUtil.resizeBitmap(resource.getBitmap(), 2048); + setImageBitmap(albumArtBitmap, RetroColorUtil.getColor(resource.getPalette(), + ContextCompat.getColor(AlbumTagEditorActivity.this, R.color.md_grey_500))); + deleteAlbumArt = false; + dataChanged(); + setResult(RESULT_OK); + } + }); + return; + } + if (lastFmAlbum.getAlbum().getTags().getTag().size() > 0) { + genre.setText(lastFmAlbum.getAlbum().getTags().getTag().get(0).getName()); + } + + } + toastLoadingFailed(); + } + + private void toastLoadingFailed() { + Toast.makeText(AlbumTagEditorActivity.this, + R.string.could_not_download_album_cover, Toast.LENGTH_SHORT).show(); + } + + @Override + protected void searchImageOnWeb() { + searchWebFor(albumTitle.getText().toString(), albumArtist.getText().toString()); + } + + @Override + protected void deleteImage() { + setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.default_album_art), + ATHUtil.resolveColor(this, R.attr.defaultFooterColor)); + deleteAlbumArt = true; + dataChanged(); + } + + @Override + protected void save() { + Map fieldKeyValueMap = new EnumMap<>(FieldKey.class); + fieldKeyValueMap.put(FieldKey.ALBUM, albumTitle.getText().toString()); + //android seems not to recognize album_artist field so we additionally write the normal artist field + fieldKeyValueMap.put(FieldKey.ARTIST, albumArtist.getText().toString()); + fieldKeyValueMap.put(FieldKey.ALBUM_ARTIST, albumArtist.getText().toString()); + fieldKeyValueMap.put(FieldKey.GENRE, genre.getText().toString()); + fieldKeyValueMap.put(FieldKey.YEAR, year.getText().toString()); + + writeValuesToFiles(fieldKeyValueMap, deleteAlbumArt ? new ArtworkInfo(getId(), null) + : albumArtBitmap == null ? null : new ArtworkInfo(getId(), albumArtBitmap)); + } + + @NonNull + @Override + protected List getSongPaths() { + ArrayList songs = AlbumLoader.getAlbum(this, getId()).blockingFirst().songs; + ArrayList paths = new ArrayList<>(songs.size()); + for (Song song : songs) { + paths.add(song.data); + } + return paths; + } + + @Override + protected void loadImageFromFile(@NonNull final Uri selectedFileUri) { + Glide.with(AlbumTagEditorActivity.this) + .load(selectedFileUri) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(AlbumTagEditorActivity.this), + BitmapPaletteWrapper.class) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .into(new SimpleTarget() { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + e.printStackTrace(); + Toast.makeText(AlbumTagEditorActivity.this, e.toString(), Toast.LENGTH_LONG).show(); + } + + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + RetroColorUtil.getColor(resource.getPalette(), Color.TRANSPARENT); + albumArtBitmap = ImageUtil.resizeBitmap(resource.getBitmap(), 2048); + setImageBitmap(albumArtBitmap, RetroColorUtil.getColor(resource.getPalette(), + ATHUtil.resolveColor(AlbumTagEditorActivity.this, R.attr.defaultFooterColor))); + deleteAlbumArt = false; + dataChanged(); + setResult(RESULT_OK); + } + }); + } + + @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(); + } + + @Override + protected void setColors(int color) { + super.setColors(color); + background.setBackgroundColor(color); + toolbar.setBackgroundColor(color); + setStatusbarColor(ColorUtil.darkenColor(color)); + setNavigationbarColor(ColorUtil.darkenColor(color)); + setSupportActionBar(toolbar); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/SongTagEditorActivity.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/SongTagEditorActivity.java new file mode 100755 index 00000000..7c194d9e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/SongTagEditorActivity.java @@ -0,0 +1,167 @@ +package code.name.monkey.retromusic.ui.activities.tageditor; + +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.AppBarLayout; +import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; +import android.widget.EditText; + +import org.jaudiotagger.tag.FieldKey; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.loaders.SongLoader; + + +public class SongTagEditorActivity extends AbsTagEditorActivity implements TextWatcher { + + public static final String TAG = SongTagEditorActivity.class.getSimpleName(); + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.app_bar) + AppBarLayout appBarLayout; + @BindView(R.id.title1) + EditText songTitle; + @BindView(R.id.title2) + EditText albumTitle; + @BindView(R.id.artist) + EditText artist; + @BindView(R.id.genre) + EditText genre; + @BindView(R.id.year) + EditText year; + @BindView(R.id.image_text) + EditText trackNumber; + @BindView(R.id.lyrics) + EditText lyrics; + @BindView(R.id.album_artist) + EditText albumArtist; + + private void setupToolbar() { + appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this)); + toolbar.setBackgroundColor(ThemeStore.primaryColor(this)); + toolbar.setNavigationOnClickListener(v -> onBackPressed()); + setTitle(R.string.action_tag_editor); + setSupportActionBar(toolbar); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ButterKnife.bind(this); + + setStatusbarColorAuto(); + setNavigationbarColorAuto(); + + setNoImageMode(); + setUpViews(); + setupToolbar(); + } + + private void setUpViews() { + fillViewsWithFileTags(); + albumArtist.addTextChangedListener(this); + songTitle.addTextChangedListener(this); + albumTitle.addTextChangedListener(this); + artist.addTextChangedListener(this); + genre.addTextChangedListener(this); + year.addTextChangedListener(this); + trackNumber.addTextChangedListener(this); + lyrics.addTextChangedListener(this); + + } + + private void fillViewsWithFileTags() { + songTitle.setText(getSongTitle()); + albumArtist.setText(getAlbumArtist()); + albumTitle.setText(getAlbumTitle()); + artist.setText(getArtistName()); + genre.setText(getGenreName()); + year.setText(getSongYear()); + trackNumber.setText(getTrackNumber()); + lyrics.setText(getLyrics()); + } + + @Override + protected void loadCurrentImage() { + + } + + @Override + protected void getImageFromLastFM() { + + } + + @Override + protected void searchImageOnWeb() { + + } + + @Override + protected void deleteImage() { + + } + + @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, artist.getText().toString()); + fieldKeyValueMap.put(FieldKey.GENRE, genre.getText().toString()); + fieldKeyValueMap.put(FieldKey.YEAR, year.getText().toString()); + fieldKeyValueMap.put(FieldKey.TRACK, trackNumber.getText().toString()); + fieldKeyValueMap.put(FieldKey.LYRICS, lyrics.getText().toString()); + fieldKeyValueMap.put(FieldKey.ALBUM_ARTIST, albumArtist.getText().toString()); + writeValuesToFiles(fieldKeyValueMap, null); + } + + @Override + protected int getContentViewLayout() { + return R.layout.activity_song_tag_editor; + } + + @NonNull + @Override + protected List getSongPaths() { + ArrayList paths = new ArrayList<>(1); + paths.add(SongLoader.getSong(this, getId()).blockingFirst().data); + return paths; + } + + @Override + protected void loadImageFromFile(Uri imageFilePath) { + + } + + @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(); + } + + @Override + protected void setColors(int color) { + super.setColors(color); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/WriteTagsAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/WriteTagsAsyncTask.java new file mode 100644 index 00000000..32c54688 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/WriteTagsAsyncTask.java @@ -0,0 +1,163 @@ +package code.name.monkey.retromusic.ui.activities.tageditor; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.media.MediaScannerConnection; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.misc.DialogAsyncTask; +import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; +import code.name.monkey.retromusic.util.MusicUtil; +import com.afollestad.materialdialogs.MaterialDialog; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +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 org.jaudiotagger.tag.images.ArtworkFactory; + +public class WriteTagsAsyncTask extends + DialogAsyncTask { + + private Context applicationContext; + + public WriteTagsAsyncTask(Context context) { + super(context); + applicationContext = context; + } + + @Override + protected String[] doInBackground(LoadingInfo... params) { + try { + LoadingInfo info = params[0]; + + Artwork artwork = null; + File albumArtFile = null; + if (info.artworkInfo != null && info.artworkInfo.artwork != null) { + try { + albumArtFile = MusicUtil.createAlbumArtFile().getCanonicalFile(); + info.artworkInfo.artwork + .compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); + artwork = ArtworkFactory.createArtworkFromFile(albumArtFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + int counter = 0; + boolean wroteArtwork = false; + boolean deletedArtwork = false; + for (String filePath : info.filePaths) { + publishProgress(++counter, info.filePaths.size()); + try { + AudioFile audioFile = AudioFileIO.read(new File(filePath)); + Tag tag = audioFile.getTagOrCreateAndSetDefault(); + + if (info.fieldKeyValueMap != null) { + for (Map.Entry entry : info.fieldKeyValueMap.entrySet()) { + try { + tag.setField(entry.getKey(), entry.getValue()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + if (info.artworkInfo != null) { + if (info.artworkInfo.artwork == null) { + tag.deleteArtworkField(); + deletedArtwork = true; + } else if (artwork != null) { + tag.deleteArtworkField(); + tag.setField(artwork); + wroteArtwork = true; + } + } + + audioFile.commit(); + } catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { + e.printStackTrace(); + } + } + + Context context = getContext(); + if (context != null) { + if (wroteArtwork) { + MusicUtil.insertAlbumArt(context, info.artworkInfo.albumId, albumArtFile.getPath()); + } else if (deletedArtwork) { + MusicUtil.deleteAlbumArt(context, info.artworkInfo.albumId); + } + } + + return info.filePaths.toArray(new String[info.filePaths.size()]); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + protected void onPostExecute(String[] toBeScanned) { + super.onPostExecute(toBeScanned); + scan(toBeScanned); + } + + @Override + protected void onCancelled(String[] toBeScanned) { + super.onCancelled(toBeScanned); + scan(toBeScanned); + } + + private void scan(String[] toBeScanned) { + Context context = getContext(); + MediaScannerConnection.scanFile(applicationContext, toBeScanned, null, + context instanceof Activity ? new UpdateToastMediaScannerCompletionListener( + (Activity) context, toBeScanned) : null); + } + + @Override + protected Dialog createDialog(@NonNull Context context) { + return new MaterialDialog.Builder(context) + .title(R.string.saving_changes) + .cancelable(false) + .progress(false, 0) + .build(); + } + + @Override + protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) { + super.onProgressUpdate(dialog, values); + ((MaterialDialog) dialog).setMaxProgress(values[1]); + ((MaterialDialog) dialog).setProgress(values[0]); + } + + public static class LoadingInfo { + + final Collection filePaths; + @Nullable + final Map fieldKeyValueMap; + @Nullable + private AbsTagEditorActivity.ArtworkInfo artworkInfo; + + public LoadingInfo(Collection filePaths, + @Nullable Map fieldKeyValueMap, + @Nullable AbsTagEditorActivity.ArtworkInfo artworkInfo) { + this.filePaths = filePaths; + this.fieldKeyValueMap = fieldKeyValueMap; + this.artworkInfo = artworkInfo; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/CollageSongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/CollageSongAdapter.java new file mode 100644 index 00000000..aa574b10 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/CollageSongAdapter.java @@ -0,0 +1,96 @@ +package code.name.monkey.retromusic.ui.adapter; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import butterknife.BindViews; +import butterknife.ButterKnife; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.ui.adapter.CollageSongAdapter.CollageSongViewHolder; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.NavigationUtil; +import com.bumptech.glide.Glide; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Hemanth S (h4h13). + */ +public class CollageSongAdapter extends RecyclerView.Adapter { + + private AppCompatActivity activity; + private ArrayList dataSet; + private int itemLayoutRes; + + public CollageSongAdapter(AppCompatActivity activity, ArrayList dataSet, + int itemLayoutRes) { + this.activity = activity; + this.dataSet = dataSet; + this.itemLayoutRes = itemLayoutRes; + } + + @Override + public void onBindViewHolder(@NonNull CollageSongViewHolder holder, int position) { + ArrayList albums = dataSet; + + holder.bindSongs(albums); + + if (albums.size() > 9) { + for (int i = 0; i < albums.subList(0, 9).size(); i++) { + if (holder.imageViews != null) { + SongGlideRequest.Builder.from(Glide.with(activity), albums.get(i).safeGetFirstSong()) + .checkIgnoreMediaStore(activity).build().into(holder.imageViews.get(i)); + } + } + } + } + + @Override + public int getItemCount() { + return 1; + } + + @NonNull + @Override + public CollageSongViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new CollageSongViewHolder( + LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)); + } + + class CollageSongViewHolder extends MediaEntryViewHolder { + + @BindViews({R.id.image_1, R.id.image_2, R.id.image_3, R.id.image_4, R.id.image_5, R.id.image_6, + R.id.image_7, R.id.image_8, R.id.image_9}) + @Nullable + List imageViews; + + CollageSongViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + @Override + public void onClick(View v) { + super.onClick(v); + NavigationUtil.goToAlbum(activity, dataSet.get(getAdapterPosition() + 1).getId()); + } + + void bindSongs(ArrayList albums) { + if (imageViews != null) { + for (int i = 0; i < imageViews.size(); i++) { + final int startPosition = i; + ImageView imageView = imageViews.get(i); + imageView.setOnClickListener( + v -> NavigationUtil.goToAlbum(activity, albums.get(startPosition).getId())); + } + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/GenreAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/GenreAdapter.java new file mode 100644 index 00000000..9f45384d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/GenreAdapter.java @@ -0,0 +1,93 @@ +package code.name.monkey.retromusic.ui.adapter; + +import android.app.Activity; +import android.graphics.PorterDuff; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Locale; + +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.NavigationUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class GenreAdapter extends RecyclerView.Adapter { + private ArrayList mGenres = new ArrayList<>(); + private Activity mActivity; + private int mItemLayoutRes; + + public GenreAdapter(@NonNull Activity activity, ArrayList dataSet, int itemLayoutRes) { + mActivity = activity; + mGenres = dataSet; + mItemLayoutRes = itemLayoutRes; + } + + public ArrayList getDataSet() { + return mGenres; + } + + @Override + public GenreAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(mActivity).inflate(mItemLayoutRes, parent, false)); + } + + @Override + public void onBindViewHolder(GenreAdapter.ViewHolder holder, int position) { + Genre genre = mGenres.get(position); + if (holder.title != null) { + holder.title.setText(genre.name); + } + if (holder.text != null) { + holder.text.setText(String.format(Locale.getDefault(), "%d %s", genre.songCount, genre.songCount > 1 ? + mActivity.getString(R.string.songs) : + mActivity.getString(R.string.song))); + } + if (holder.image != null) { + holder.image.setImageResource(R.drawable.ic_recent_actors_white_24dp); + } + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.VISIBLE); + } + } + + @Override + public int getItemCount() { + return mGenres.size(); + } + + public void swapDataSet(ArrayList list) { + mGenres = list; + notifyDataSetChanged(); + } + + public class ViewHolder extends MediaEntryViewHolder { + public ViewHolder(View itemView) { + super(itemView); + if (menu != null) { + menu.setVisibility(View.GONE); + } + if (image != null) { + int iconPadding = mActivity.getResources().getDimensionPixelSize(R.dimen.list_item_image_icon_padding); + image.setPadding(iconPadding, iconPadding, iconPadding, iconPadding); + image.setColorFilter(ATHUtil.resolveColor(mActivity, R.attr.iconColor), PorterDuff.Mode.SRC_IN); + } + } + + @Override + public void onClick(View v) { + super.onClick(v); + Genre genre = mGenres.get(getAdapterPosition()); + NavigationUtil.goToGenre(mActivity, genre); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/SearchAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/SearchAdapter.java new file mode 100644 index 00000000..75706a95 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/SearchAdapter.java @@ -0,0 +1,163 @@ +package code.name.monkey.retromusic.ui.adapter; + +import android.support.annotation.NonNull; +import android.support.v4.util.Pair; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; +import java.util.List; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.ArtistGlideRequest; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.menu.SongMenuHelper; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; + + +public class SearchAdapter extends RecyclerView.Adapter { + + private static final int HEADER = 0; + private static final int ALBUM = 1; + private static final int ARTIST = 2; + private static final int SONG = 3; + + private final AppCompatActivity activity; + private List dataSet; + + public SearchAdapter(@NonNull AppCompatActivity activity, @NonNull List dataSet) { + this.activity = activity; + this.dataSet = dataSet; + } + + public void swapDataSet(@NonNull ArrayList dataSet) { + this.dataSet = dataSet; + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + if (dataSet.get(position) instanceof Album) return ALBUM; + if (dataSet.get(position) instanceof Artist) return ARTIST; + if (dataSet.get(position) instanceof Song) return SONG; + return HEADER; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == HEADER) + return new ViewHolder(LayoutInflater.from(activity).inflate(R.layout.sub_header, parent, false), viewType); + return new ViewHolder(LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false), viewType); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { + switch (getItemViewType(position)) { + case ALBUM: + final Album album = (Album) dataSet.get(position); + holder.title.setText(album.getTitle()); + holder.text.setText(album.getArtistName()); + SongGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) + .checkIgnoreMediaStore(activity).build() + .into(holder.image); + break; + case ARTIST: + final Artist artist = (Artist) dataSet.get(position); + holder.title.setText(artist.getName()); + holder.text.setText(MusicUtil.getArtistInfoString(activity, artist)); + ArtistGlideRequest.Builder.from(Glide.with(activity), artist) + .build().into(holder.image); + break; + case SONG: + final Song song = (Song) dataSet.get(position); + holder.title.setText(song.title); + holder.text.setText(song.albumName); + break; + default: + holder.title.setText(dataSet.get(position).toString()); + holder.title.setTextColor(ThemeStore.accentColor(activity)); + break; + } + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + public class ViewHolder extends MediaEntryViewHolder { + public ViewHolder(@NonNull View itemView, int itemViewType) { + super(itemView); + itemView.setOnLongClickListener(null); + + if (itemViewType != HEADER) { + if (separator != null) { + separator.setVisibility(View.GONE); + } + } + + if (menu != null) { + if (itemViewType == SONG) { + menu.setVisibility(View.VISIBLE); + menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu(activity) { + @Override + public Song getSong() { + return (Song) dataSet.get(getAdapterPosition()); + } + }); + } else { + menu.setVisibility(View.GONE); + } + } + + switch (itemViewType) { + case ALBUM: + setImageTransitionName(activity.getString(R.string.transition_album_art)); + break; + case ARTIST: + setImageTransitionName(activity.getString(R.string.transition_artist_image)); + break; + default: + View container = itemView.findViewById(R.id.image_container); + if (container != null) { + container.setVisibility(View.GONE); + } + break; + } + } + + @Override + public void onClick(View view) { + Object item = dataSet.get(getAdapterPosition()); + switch (getItemViewType()) { + case ALBUM: + NavigationUtil.goToAlbum(activity, + ((Album) item).getId(), Pair.create(image, activity.getResources().getString(R.string.transition_album_art))); + break; + case ARTIST: + NavigationUtil.goToArtist(activity, + ((Artist) item).getId(), Pair.create(image, activity.getResources().getString(R.string.transition_artist_image))); + break; + case SONG: + ArrayList playList = new ArrayList<>(); + playList.add((Song) item); + MusicPlayerRemote.openQueue(playList, 0, true); + break; + } + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/SongFileAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/SongFileAdapter.java new file mode 100644 index 00000000..c44a8184 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/SongFileAdapter.java @@ -0,0 +1,214 @@ +package code.name.monkey.retromusic.ui.adapter; + +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.signature.MediaStoreSignature; +import code.name.monkey.appthemehelper.util.ATHUtil; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import java.io.File; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.audiocover.AudioFileCover; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.ui.adapter.base.AbsMultiSelectAdapter; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.RetroUtil; + +public class SongFileAdapter extends AbsMultiSelectAdapter implements FastScrollRecyclerView.SectionedAdapter { + + private static final int FILE = 0; + private static final int FOLDER = 1; + + private final AppCompatActivity activity; + private List dataSet; + private final int itemLayoutRes; + @Nullable + private final Callbacks callbacks; + + public SongFileAdapter(@NonNull AppCompatActivity activity, @NonNull List songFiles, @LayoutRes int itemLayoutRes, @Nullable Callbacks callback, @Nullable CabHolder cabHolder) { + super(activity, cabHolder, R.menu.menu_media_selection); + this.activity = activity; + this.dataSet = songFiles; + this.itemLayoutRes = itemLayoutRes; + this.callbacks = callback; + setHasStableIds(true); + } + + @Override + public int getItemViewType(int position) { + return dataSet.get(position).isDirectory() ? FOLDER : FILE; + } + + @Override + public long getItemId(int position) { + return dataSet.get(position).hashCode(); + } + + public void swapDataSet(@NonNull List songFiles) { + this.dataSet = songFiles; + notifyDataSetChanged(); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)); + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int index) { + final File file = dataSet.get(index); + + holder.itemView.setActivated(isChecked(file)); + + if (holder.getAdapterPosition() == getItemCount() - 1) { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.GONE); + } + } else { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.VISIBLE); + } + } + + if (holder.title != null) { + holder.title.setText(getFileTitle(file)); + } + if (holder.text != null) { + if (holder.getItemViewType() == FILE) { + holder.text.setText(getFileText(file)); + } else { + holder.text.setVisibility(View.GONE); + } + } + + if (holder.image != null) { + loadFileImage(file, holder); + } + } + + protected String getFileTitle(File file) { + return file.getName(); + } + + protected String getFileText(File file) { + return file.isDirectory() ? null : readableFileSize(file.length()); + } + + @SuppressWarnings("ConstantConditions") + protected void loadFileImage(File file, final ViewHolder holder) { + final int iconColor = ATHUtil.resolveColor(activity, R.attr.iconColor); + if (file.isDirectory()) { + holder.image.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); + holder.image.setImageResource(R.drawable.ic_folder_white_24dp); + } else { + Drawable error = RetroUtil.getTintedVectorDrawable(activity, R.drawable.ic_file_music_white_24dp, iconColor); + Glide.with(activity) + .load(new AudioFileCover(file.getPath())) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .error(error) + .placeholder(error) + .animate(android.R.anim.fade_in) + .signature(new MediaStoreSignature("", file.lastModified(), 0)) + .into(holder.image); + } + } + + public static String readableFileSize(long size) { + if (size <= 0) return size + " B"; + final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"}; + int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); + return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + @Override + protected File getIdentifier(int position) { + return dataSet.get(position); + } + + @Override + protected String getName(File object) { + return getFileTitle(object); + } + + @Override + protected void onMultipleItemAction(MenuItem menuItem, ArrayList selection) { + if (callbacks == null) return; + callbacks.onMultipleItemAction(menuItem, selection); + } + + @NonNull + @Override + public String getSectionName(int position) { + return String.valueOf(dataSet.get(position).getName().charAt(0)).toUpperCase(); + } + + public class ViewHolder extends MediaEntryViewHolder { + + public ViewHolder(View itemView) { + super(itemView); + if (menu != null && callbacks != null) { + menu.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int position = getAdapterPosition(); + if (isPositionInRange(position)) { + callbacks.onFileMenuClicked(dataSet.get(position), v); + } + } + }); + } + } + + @Override + public void onClick(View v) { + int position = getAdapterPosition(); + if (isPositionInRange(position)) { + if (isInQuickSelectMode()) { + toggleChecked(position); + } else { + if (callbacks != null) { + callbacks.onFileSelected(dataSet.get(position)); + } + } + } + } + + @Override + public boolean onLongClick(View view) { + int position = getAdapterPosition(); + return isPositionInRange(position) && toggleChecked(position); + } + + private boolean isPositionInRange(int position) { + return position >= 0 && position < dataSet.size(); + } + } + + public interface Callbacks { + void onFileSelected(File file); + + void onFileMenuClicked(File file, View view); + + void onMultipleItemAction(MenuItem item, ArrayList files); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumAdapter.java new file mode 100644 index 00000000..eb0eff06 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumAdapter.java @@ -0,0 +1,258 @@ +package code.name.monkey.retromusic.ui.adapter.album; + +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.Pair; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.SortOrder; +import code.name.monkey.retromusic.helper.menu.SongsMenuHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.adapter.base.AbsMultiSelectAdapter; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + + +public class AlbumAdapter extends AbsMultiSelectAdapter implements + FastScrollRecyclerView.SectionedAdapter { + + public static final String TAG = AlbumAdapter.class.getSimpleName(); + + protected final AppCompatActivity activity; + protected ArrayList dataSet; + + protected int itemLayoutRes; + + protected boolean usePalette = false; + private int textColor; + + public AlbumAdapter(@NonNull AppCompatActivity activity, ArrayList dataSet, + @LayoutRes int itemLayoutRes, boolean usePalette, + @Nullable CabHolder cabHolder) { + super(activity, cabHolder, R.menu.menu_media_selection); + this.activity = activity; + this.dataSet = dataSet; + this.itemLayoutRes = itemLayoutRes; + this.usePalette = usePalette; + setHasStableIds(true); + this.textColor = ThemeStore.textColorPrimary(activity); + Typeface mTypeface = Typeface + .createFromAsset(activity.getAssets(), activity.getString(R.string.sans_regular)); + } + + public void useItemLayout(int itemLayoutRes) { + this.itemLayoutRes = itemLayoutRes; + notifyDataSetChanged(); + } + + public void usePalette(boolean usePalette) { + this.usePalette = usePalette; + notifyDataSetChanged(); + } + + public void swapDataSet(ArrayList dataSet) { + this.dataSet = dataSet; + notifyDataSetChanged(); + } + + public ArrayList getDataSet() { + return dataSet; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false); + return createViewHolder(view, viewType); + } + + protected ViewHolder createViewHolder(View view, int viewType) { + return new ViewHolder(view); + } + + private String getAlbumTitle(Album album) { + return album.getTitle(); + } + + protected String getAlbumText(Album album) { + return album.getArtistName(); + } + + @Override + public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { + final Album album = dataSet.get(position); + + final boolean isChecked = isChecked(album); + holder.itemView.setActivated(isChecked); + + if (holder.getAdapterPosition() == getItemCount() - 1) { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.GONE); + } + } else { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.VISIBLE); + } + } + + if (holder.title != null) { + holder.title.setText(getAlbumTitle(album)); + holder.title.setTextColor(textColor); + } + if (holder.text != null) { + holder.text.setText(getAlbumText(album)); + } + if (holder.playSongs != null) { + holder.playSongs.setOnClickListener(v -> MusicPlayerRemote.openQueue(album.songs, 0, true)); + } + loadAlbumCover(album, holder); + } + + protected void setColors(int color, ViewHolder holder) { + if (holder.paletteColorContainer != null) { + holder.paletteColorContainer.setBackgroundColor(color); + if (holder.title != null) { + holder.title.setTextColor( + MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color))); + } + if (holder.text != null) { + holder.text.setTextColor( + MaterialValueHelper.getSecondaryTextColor(activity, ColorUtil.isColorLight(color))); + } + } + } + + protected void loadAlbumCover(Album album, final ViewHolder holder) { + if (holder.image == null) { + return; + } + + SongGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) + .checkIgnoreMediaStore(activity) + .generatePalette(activity).build() + .into(new RetroMusicColoredTarget(holder.image) { + @Override + public void onLoadCleared(Drawable placeholder) { + super.onLoadCleared(placeholder); + setColors(getDefaultFooterColor(), holder); + } + + @Override + public void onColorReady(int color) { + setColors(color, holder); + } + }); + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + @Override + public long getItemId(int position) { + return dataSet.get(position).getId(); + } + + @Override + protected Album getIdentifier(int position) { + return dataSet.get(position); + } + + @Override + protected String getName(Album album) { + return album.getTitle(); + } + + @Override + protected void onMultipleItemAction(@NonNull MenuItem menuItem, + @NonNull ArrayList selection) { + SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId()); + } + + @NonNull + private ArrayList getSongList(@NonNull List albums) { + final ArrayList songs = new ArrayList<>(); + for (Album album : albums) { + songs.addAll(album.songs); + } + return songs; + } + + @NonNull + @Override + public String getSectionName(int position) { + @Nullable String sectionName = null; + switch (PreferenceUtil.getInstance(activity).getAlbumSortOrder()) { + case SortOrder.AlbumSortOrder.ALBUM_A_Z: + case SortOrder.AlbumSortOrder.ALBUM_Z_A: + sectionName = dataSet.get(position).getTitle(); + break; + case SortOrder.AlbumSortOrder.ALBUM_ARTIST: + sectionName = dataSet.get(position).getArtistName(); + break; + case SortOrder.AlbumSortOrder.ALBUM_YEAR: + return MusicUtil.getYearString(dataSet.get(position).getYear()); + } + + return MusicUtil.getSectionName(sectionName); + } + + public void setTextColor(int textColor) { + this.textColor = textColor; + notifyDataSetChanged(); + } + + + public class ViewHolder extends MediaEntryViewHolder { + + public ViewHolder(@NonNull final View itemView) { + super(itemView); + setImageTransitionName(activity.getString(R.string.transition_album_art)); + if (menu != null) { + menu.setVisibility(View.GONE); + } + } + + @Override + public void onClick(View v) { + if (isInQuickSelectMode()) { + toggleChecked(getAdapterPosition()); + } else { + Pair[] albumPairs = new Pair[]{ + Pair.create(image, activity.getResources().getString(R.string.transition_album_art))}; + NavigationUtil.goToAlbum(activity, dataSet.get(getAdapterPosition()).getId(), albumPairs); + } + } + + @Override + public boolean onLongClick(View view) { + toggleChecked(getAdapterPosition()); + return true; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumCoverPagerAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumCoverPagerAdapter.java new file mode 100644 index 00000000..17b12b05 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumCoverPagerAdapter.java @@ -0,0 +1,208 @@ +package code.name.monkey.retromusic.ui.adapter.album; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; + +import java.util.ArrayList; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.activities.LyricsActivity; +import code.name.monkey.retromusic.ui.fragments.NowPlayingScreen; +import code.name.monkey.retromusic.util.PreferenceUtil; + + +public class AlbumCoverPagerAdapter extends CustomFragmentStatePagerAdapter { + + public static final String TAG = AlbumCoverPagerAdapter.class.getSimpleName(); + + private ArrayList dataSet; + + private AlbumCoverFragment.ColorReceiver currentColorReceiver; + private int currentColorReceiverPosition = -1; + + public AlbumCoverPagerAdapter(FragmentManager fm, ArrayList dataSet) { + super(fm); + this.dataSet = dataSet; + } + + @Override + public Fragment getItem(final int position) { + return AlbumCoverFragment.newInstance(dataSet.get(position)); + } + + @Override + public int getCount() { + return dataSet.size(); + } + + @NonNull + @Override + public Object instantiateItem(ViewGroup container, int position) { + Object o = super.instantiateItem(container, position); + if (currentColorReceiver != null && currentColorReceiverPosition == position) { + receiveColor(currentColorReceiver, currentColorReceiverPosition); + } + return o; + } + + /** + * Only the latest passed {@link AlbumCoverFragment.ColorReceiver} is guaranteed to receive a + * response + */ + public void receiveColor(AlbumCoverFragment.ColorReceiver colorReceiver, int position) { + AlbumCoverFragment fragment = (AlbumCoverFragment) getFragment(position); + if (fragment != null) { + currentColorReceiver = null; + currentColorReceiverPosition = -1; + fragment.receiveColor(colorReceiver, position); + } else { + currentColorReceiver = colorReceiver; + currentColorReceiverPosition = position; + } + } + + public static class AlbumCoverFragment extends Fragment { + + private static final String SONG_ARG = "song"; + @BindView(R.id.player_image) + ImageView albumCover; + private Unbinder unbinder; + private boolean isColorReady; + private int color; + private Song song; + private ColorReceiver colorReceiver; + private int request; + + public static AlbumCoverFragment newInstance(final Song song) { + AlbumCoverFragment frag = new AlbumCoverFragment(); + final Bundle args = new Bundle(); + args.putParcelable(SONG_ARG, song); + frag.setArguments(args); + return frag; + } + + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + song = getArguments().getParcelable(SONG_ARG); + } + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + int layout = getLayout(); + View view = inflater.inflate(layout, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + private int getLayout() { + int layout; + //noinspection ConstantConditions + switch (PreferenceUtil.getInstance(getContext()).getNowPlayingScreen()) { + case BLUR_CARD: + layout = R.layout.fragment_album_card_cover; + break; + case MATERIAL: + layout = R.layout.fragment_album_material_cover; + break; + case PLAIN: + case FLAT: + layout = R.layout.fragment_album_flat_cover; + break; + case CARD: + case FULL: + case ADAPTIVE: + layout = R.layout.fragment_album_full_cover; + break; + default: + case NORMAL: + layout = R.layout.fragment_album_cover; + break; + + } + if (PreferenceUtil.getInstance(getContext()).carouselEffect() && + !(PreferenceUtil.getInstance(getContext()).getNowPlayingScreen() + == NowPlayingScreen.FULL)) { + layout = R.layout.fragment_carousal_album_cover; + } + if (PreferenceUtil.getInstance(getContext()).circularAlbumArt()) { + layout = R.layout.fragment_album_circle_cover; + } + return layout; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + loadAlbumCover(); + } + + @OnClick(R.id.player_image) + void showLyrics() { + startActivity(new Intent(getContext(), LyricsActivity.class)); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + colorReceiver = null; + } + + private void loadAlbumCover() { + SongGlideRequest.Builder.from(Glide.with(this), song) + .checkIgnoreMediaStore(getActivity()) + .generatePalette(getActivity()).build() + .into(new RetroMusicColoredTarget(albumCover) { + @Override + public void onColorReady(int color) { + setColor(color); + } + }); + } + + private void setColor(int color) { + this.color = color; + isColorReady = true; + if (colorReceiver != null) { + colorReceiver.onColorReady(color, request); + colorReceiver = null; + } + } + + public void receiveColor(ColorReceiver colorReceiver, int request) { + if (isColorReady) { + colorReceiver.onColorReady(color, request); + } else { + this.colorReceiver = colorReceiver; + this.request = request; + } + } + + public interface ColorReceiver { + + void onColorReady(int color, int request); + } + } +} + diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumFullWithAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumFullWithAdapter.java new file mode 100644 index 00000000..57d2b2a1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/AlbumFullWithAdapter.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017. Alexander Bilchuk + * + * 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 code.name.monkey.retromusic.ui.adapter.album; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.support.annotation.NonNull; +import android.support.v4.util.Pair; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.views.MetalRecyclerViewPager; +import com.bumptech.glide.Glide; +import java.util.ArrayList; +import java.util.List; + +public class AlbumFullWithAdapter extends + MetalRecyclerViewPager.MetalAdapter { + + private Activity activity; + private List dataSet = new ArrayList<>(); + + public AlbumFullWithAdapter(@NonNull Activity activity, + @NonNull DisplayMetrics metrics) { + super(metrics); + this.activity = activity; + } + + public void swapData(ArrayList list) { + dataSet = list; + notifyDataSetChanged(); + } + + @Override + public FullMetalViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View viewItem = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.pager_item, parent, false); + return new FullMetalViewHolder(viewItem); + } + + private Bitmap combineImageIntoOne(ArrayList bitmap) { + int w = 0, h = 0; + for (int i = 0; i < bitmap.size(); i++) { + if (i < bitmap.size() - 1) { + h = bitmap.get(i).getWidth() > bitmap.get(i + 1).getWidth() ? bitmap.get(i).getWidth() + : bitmap.get(i + 1).getWidth(); + } + w += bitmap.get(i).getHeight(); + } + + Bitmap temp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(temp); + int top = 0, left = 0; + for (int i = 0; i < bitmap.size(); i++) { + Log.d("HTML", "Combine: " + i + "/" + bitmap.size() + 1); + + top = (i == 0 ? 0 : top + bitmap.get(i).getHeight()); + left = (i == 0 ? 0 : top + bitmap.get(i).getWidth()); + canvas.drawBitmap(bitmap.get(i), left, 0f, null); + } + return temp; + } + + @Override + public void onBindViewHolder(FullMetalViewHolder holder, int position) { + // don't forget about calling supper.onBindViewHolder! + super.onBindViewHolder(holder, position); + + final Album album = dataSet.get(position); + + if (holder.title != null) { + holder.title.setText(getAlbumTitle(album)); + } + if (holder.text != null) { + holder.text.setText(getAlbumText(album)); + } + if (holder.playSongs != null) { + holder.playSongs.setOnClickListener(v -> MusicPlayerRemote.openQueue(album.songs, 0, true)); + } + loadAlbumCover(album, holder); + } + + private String getAlbumTitle(Album album) { + return album.getTitle(); + } + + private String getAlbumText(Album album) { + return album.getArtistName(); + } + + private void loadAlbumCover(Album album, FullMetalViewHolder holder) { + if (holder.image == null) { + return; + } + + SongGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) + .checkIgnoreMediaStore(activity) + .generatePalette(activity).build() + .into(new RetroMusicColoredTarget(holder.image) { + @Override + public void onColorReady(int color) { + + } + }); + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + class FullMetalViewHolder extends MetalRecyclerViewPager.MetalViewHolder { + + FullMetalViewHolder(View itemView) { + super(itemView); + } + + @Override + public void onClick(View v) { + Pair[] albumPairs = new Pair[]{ + Pair.create(image, activity.getResources().getString(R.string.transition_album_art))}; + NavigationUtil.goToAlbum(activity, dataSet.get(getAdapterPosition()).getId(), albumPairs); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/HorizontalAlbumAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/HorizontalAlbumAdapter.java new file mode 100644 index 00000000..3007e6db --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/album/HorizontalAlbumAdapter.java @@ -0,0 +1,83 @@ +package code.name.monkey.retromusic.ui.adapter.album; + +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; + +import java.util.ArrayList; + +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.HorizontalAdapterHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.util.MusicUtil; + + +public class HorizontalAlbumAdapter extends AlbumAdapter { + public static final String TAG = AlbumAdapter.class.getSimpleName(); + + public HorizontalAlbumAdapter(@NonNull AppCompatActivity activity, ArrayList dataSet, boolean usePalette, @Nullable CabHolder cabHolder) { + super(activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, usePalette, cabHolder); + } + + @Override + protected ViewHolder createViewHolder(View view, int viewType) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + HorizontalAdapterHelper.applyMarginToLayoutParams(activity, params, viewType); + return new ViewHolder(view); + } + + @Override + protected void setColors(int color, ViewHolder holder) { + if (holder.itemView != null) { + if (holder.title != null) { + holder.title.setTextColor(MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color))); + } + if (holder.text != null) { + holder.text.setTextColor(MaterialValueHelper.getSecondaryTextColor(activity, ColorUtil.isColorLight(color))); + } + } + } + + @Override + protected void loadAlbumCover(Album album, final ViewHolder holder) { + if (holder.image == null) return; + + SongGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong()) + .checkIgnoreMediaStore(activity) + .generatePalette(activity).build() + .into(new RetroMusicColoredTarget(holder.image) { + @Override + public void onLoadCleared(Drawable placeholder) { + super.onLoadCleared(placeholder); + setColors(getAlbumArtistFooterColor(), holder); + } + + @Override + public void onColorReady(int color) { + if (usePalette) + setColors(color, holder); + else + setColors(getAlbumArtistFooterColor(), holder); + } + }); + } + + @Override + protected String getAlbumText(Album album) { + return MusicUtil.getYearString(album.getYear()); + } + + @Override + public int getItemViewType(int position) { + return HorizontalAdapterHelper.getItemViewtype(position, getItemCount()); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/artist/ArtistAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/artist/ArtistAdapter.java new file mode 100644 index 00000000..58f921e7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/artist/ArtistAdapter.java @@ -0,0 +1,188 @@ +package code.name.monkey.retromusic.ui.adapter.artist; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.Pair; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.ArtistGlideRequest; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.helper.menu.SongsMenuHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.adapter.base.AbsMultiSelectAdapter; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; +import com.bumptech.glide.Glide; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; +import java.util.ArrayList; +import java.util.List; + + +public class ArtistAdapter extends + AbsMultiSelectAdapter implements + FastScrollRecyclerView.SectionedAdapter { + + protected final AppCompatActivity activity; + protected ArrayList dataSet; + + protected int itemLayoutRes; + + protected boolean usePalette = false; + + + public ArtistAdapter(@NonNull AppCompatActivity activity, ArrayList dataSet, + @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) { + super(activity, cabHolder, R.menu.menu_media_selection); + this.activity = activity; + this.dataSet = dataSet; + this.itemLayoutRes = itemLayoutRes; + this.usePalette = usePalette; + //setHasStableIds(true); + } + + public void swapDataSet(ArrayList dataSet) { + this.dataSet = dataSet; + notifyDataSetChanged(); + } + + public ArrayList getDataSet() { + return dataSet; + } + + public void usePalette(boolean usePalette) { + this.usePalette = usePalette; + notifyDataSetChanged(); + } + + @Override + public long getItemId(int position) { + return dataSet.get(position).getId(); + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false); + return createViewHolder(view); + } + + protected ViewHolder createViewHolder(View view) { + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { + final Artist artist = dataSet.get(position); + + boolean isChecked = isChecked(artist); + holder.itemView.setActivated(isChecked); + + if (holder.title != null) { + holder.title.setText(artist.getName()); + } + if (holder.text != null) { + holder.text.setVisibility(View.GONE); + } + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.VISIBLE); + } + loadArtistImage(artist, holder); + } + + protected void setColors(int color, ViewHolder holder) { + if (holder.paletteColorContainer != null) { + holder.paletteColorContainer.setBackgroundColor(color); + if (holder.title != null) { + holder.title.setTextColor( + MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color))); + } + } + } + + private void loadArtistImage(Artist artist, final ViewHolder holder) { + if (holder.image == null) { + return; + } + ArtistGlideRequest.Builder.from(Glide.with(activity), artist) + .generatePalette(activity).build() + .into(new RetroMusicColoredTarget(holder.image) { + @Override + public void onColorReady(int color) { + setColors(color, holder); + } + }); + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + @Override + protected Artist getIdentifier(int position) { + return dataSet.get(position); + } + + @Override + protected String getName(Artist artist) { + return artist.getName(); + } + + @Override + protected void onMultipleItemAction(@NonNull MenuItem menuItem, + @NonNull ArrayList selection) { + SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId()); + } + + @NonNull + private ArrayList getSongList(@NonNull List artists) { + final ArrayList songs = new ArrayList<>(); + for (Artist artist : artists) { + songs.addAll(artist.getSongs()); // maybe async in future? + } + return songs; + } + + @NonNull + @Override + public String getSectionName(int position) { + return MusicUtil.getSectionName(dataSet.get(position).getName()); + } + + public class ViewHolder extends MediaEntryViewHolder { + + public ViewHolder(@NonNull View itemView) { + super(itemView); + setImageTransitionName(activity.getString(R.string.transition_artist_image)); + if (menu != null) { + menu.setVisibility(View.GONE); + } + } + + @Override + public void onClick(View v) { + if (isInQuickSelectMode()) { + toggleChecked(getAdapterPosition()); + } else { + Pair[] artistPairs = new Pair[]{Pair.create(image, + activity.getResources().getString(R.string.transition_artist_image))}; + NavigationUtil.goToArtist(activity, dataSet.get(getAdapterPosition()).getId(), artistPairs); + } + } + + @Override + public boolean onLongClick(View view) { + toggleChecked(getAdapterPosition()); + return true; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/base/AbsMultiSelectAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/base/AbsMultiSelectAdapter.java new file mode 100644 index 00000000..acb29829 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/base/AbsMultiSelectAdapter.java @@ -0,0 +1,121 @@ +package code.name.monkey.retromusic.ui.adapter.base; + +import android.content.Context; +import android.support.annotation.MenuRes; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuItem; + +import com.afollestad.materialcab.MaterialCab; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.interfaces.CabHolder; + + +public abstract class AbsMultiSelectAdapter extends RecyclerView.Adapter implements MaterialCab.Callback { + @Nullable + private final CabHolder cabHolder; + private final Context context; + private MaterialCab cab; + private ArrayList checked; + private int menuRes; + + public AbsMultiSelectAdapter(Context context, @Nullable CabHolder cabHolder, @MenuRes int menuRes) { + this.cabHolder = cabHolder; + checked = new ArrayList<>(); + this.menuRes = menuRes; + this.context = context; + } + + protected void setMultiSelectMenuRes(@MenuRes int menuRes) { + this.menuRes = menuRes; + } + + protected boolean toggleChecked(final int position) { + if (cabHolder != null) { + I identifier = getIdentifier(position); + if (identifier == null) return false; + + if (!checked.remove(identifier)) checked.add(identifier); + + notifyItemChanged(position); + updateCab(); + return true; + } + return false; + } + + protected void checkAll() { + if (cabHolder != null) { + checked.clear(); + for (int i = 0; i < getItemCount(); i++) { + I identifier = getIdentifier(i); + if (identifier != null) { + checked.add(identifier); + } + } + notifyDataSetChanged(); + updateCab(); + } + } + + private void updateCab() { + if (cabHolder != null) { + if (cab == null || !cab.isActive()) { + cab = cabHolder.openCab(menuRes, this); + } + final int size = checked.size(); + if (size <= 0) cab.finish(); + else if (size == 1) cab.setTitle(getName(checked.get(0))); + else cab.setTitle(context.getString(R.string.x_selected, size)); + } + } + + private void clearChecked() { + checked.clear(); + notifyDataSetChanged(); + } + + protected boolean isChecked(I identifier) { + return checked.contains(identifier); + } + + protected boolean isInQuickSelectMode() { + return cab != null && cab.isActive(); + } + + @Override + public boolean onCabCreated(MaterialCab materialCab, Menu menu) { + return true; + } + + @Override + public boolean onCabItemClicked(MenuItem menuItem) { + if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) { + checkAll(); + } else { + onMultipleItemAction(menuItem, new ArrayList<>(checked)); + cab.finish(); + clearChecked(); + } + return true; + } + + @Override + public boolean onCabFinished(MaterialCab materialCab) { + clearChecked(); + return true; + } + + protected String getName(I object) { + return object.toString(); + } + + @Nullable + protected abstract I getIdentifier(int position); + + protected abstract void onMultipleItemAction(MenuItem menuItem, ArrayList selection); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/base/MediaEntryViewHolder.java new file mode 100644 index 00000000..5659aac7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/base/MediaEntryViewHolder.java @@ -0,0 +1,98 @@ +package code.name.monkey.retromusic.ui.adapter.base; + +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.CardView; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; +import butterknife.BindView; +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; + + +public class MediaEntryViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener, View.OnLongClickListener { + + @Nullable + @BindView(R.id.image) + public ImageView image; + @Nullable + @BindView(R.id.image_text) + public TextView imageText; + @Nullable + @BindView(R.id.title) + public TextView title; + @Nullable + @BindView(R.id.text) + public TextView text; + @Nullable + @BindView(R.id.image_container) + public ViewGroup imageContainer; + @Nullable + @BindView(R.id.image_container_card) + public CardView imageContainerCard; + @Nullable + @BindView(R.id.menu) + public View menu; + @Nullable + @BindView(R.id.separator) + public View separator; + @Nullable + @BindView(R.id.short_separator) + public View shortSeparator; + @Nullable + @BindView(R.id.drag_view) + public View dragView; + @Nullable + @BindView(R.id.palette_color_container) + public View paletteColorContainer; + @BindView(R.id.time) + @Nullable + public TextView time; + @BindView(R.id.recycler_view) + @Nullable + public RecyclerView recyclerView; + @BindView(R.id.play_songs) + @Nullable + public ImageButton playSongs; + @BindView(R.id.image_text_container) + @Nullable + public CardView imageTextContainer; + + + public MediaEntryViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + + if (imageTextContainer != null) { + imageTextContainer.setCardBackgroundColor(ThemeStore.primaryColor(itemView.getContext())); + } + if (imageContainerCard != null) { + imageContainerCard.setCardBackgroundColor(ThemeStore.primaryColor(itemView.getContext())); + } + } + + protected void setImageTransitionName(@NonNull String transitionName) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && image != null) { + image.setTransitionName(transitionName); + } + } + + @Override + public boolean onLongClick(View v) { + return false; + } + + @Override + public void onClick(View v) { + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/home/HomeAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/home/HomeAdapter.java new file mode 100644 index 00000000..d8ac7f7e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/home/HomeAdapter.java @@ -0,0 +1,169 @@ +package code.name.monkey.retromusic.ui.adapter.home; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import butterknife.BindView; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.model.smartplaylist.HistoryPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist; +import code.name.monkey.retromusic.ui.adapter.album.AlbumAdapter; +import code.name.monkey.retromusic.ui.adapter.artist.ArtistAdapter; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.ui.adapter.song.SongAdapter; +import code.name.monkey.retromusic.util.NavigationUtil; +import java.util.ArrayList; + +public class HomeAdapter extends RecyclerView.Adapter { + + private static final int SUB_HEADER = 0; + private static final int ABS_PLAYLITS = 1; + private static final int DATA = 2; + private ArrayList dataSet = new ArrayList<>(); + private AppCompatActivity activity; + + public HomeAdapter(@NonNull AppCompatActivity activity) { + this.activity = activity; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + switch (i) { + + case ABS_PLAYLITS: + return new ViewHolder(LayoutInflater.from(activity) + .inflate(R.layout.abs_playlists, viewGroup, false)); + default: + case DATA: + return new ViewHolder(LayoutInflater.from(activity) + .inflate(R.layout.recycler_view_sec, viewGroup, false)); + case SUB_HEADER: + return new ViewHolder(LayoutInflater.from(activity) + .inflate(R.layout.sub_header, viewGroup, false)); + } + } + + @Override + public int getItemViewType(int position) { + if (dataSet.get(position) instanceof String) { + return SUB_HEADER; + } else if (dataSet.get(position) instanceof Integer) { + return ABS_PLAYLITS; + } else if (dataSet.get(position) instanceof ArrayList) { + return DATA; + } + return super.getItemViewType(position); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { + ViewHolder viewholder = (ViewHolder) holder; + switch (getItemViewType(i)) { + case ABS_PLAYLITS: + bindAbsActions(viewholder); + break; + case SUB_HEADER: + String title = (String) dataSet.get(i); + if (viewholder.title != null) { + viewholder.title.setText(title); + } + break; + case DATA: + parseAllSections(i, viewholder); + break; + } + } + + private void bindAbsActions(ViewHolder viewholder) { + + + if (viewholder.shuffle != null) { + viewholder.shuffle.setOnClickListener(view -> MusicPlayerRemote + .openAndShuffleQueue(SongLoader.getAllSongs(activity).blockingFirst(), true)); + } + /*if (viewholder.search != null) { + viewholder.search.setBackgroundTintList(ColorStateList.valueOf(ColorUtil.withAlpha(ThemeStore.textColorPrimary(activity), 0.2f))); + viewholder.search.setOnClickListener(view -> { + activity.startActivity(new Intent(activity, SearchActivity.class)); + }); + }*/ + } + + @SuppressWarnings("unchecked") + private void parseAllSections(int i, ViewHolder viewholder) { + if (viewholder.recyclerView != null) { + ArrayList arrayList = (ArrayList) dataSet.get(i); + if (arrayList.isEmpty()) { + return; + } + Object something = arrayList.get(0); + if (something instanceof Artist) { + layoutManager(viewholder); + viewholder.recyclerView.setAdapter( + new ArtistAdapter(activity, (ArrayList) arrayList, R.layout.item_artist, false, + null)); + } else if (something instanceof Album) { + layoutManager(viewholder); + viewholder.recyclerView.setItemAnimator(new DefaultItemAnimator()); + viewholder.recyclerView.setAdapter( + new AlbumAdapter(activity, (ArrayList) arrayList, R.layout.item_image, false, + null)); + } else if (something instanceof Song) { + GridLayoutManager layoutManager = new GridLayoutManager(activity, 1, + LinearLayoutManager.HORIZONTAL, false); + viewholder.recyclerView.setLayoutManager(layoutManager); + viewholder.recyclerView.setAdapter( + new SongAdapter(activity, (ArrayList) arrayList, R.layout.item_image, false, + null)); + } + } + } + + private void layoutManager(ViewHolder viewholder) { + if (viewholder.recyclerView != null) { + viewholder.recyclerView.setLayoutManager( + new GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)); + viewholder.recyclerView.setItemAnimator(new DefaultItemAnimator()); + } + } + + + @Override + public int getItemCount() { + return dataSet.size(); + } + + public void swapDataSet(@NonNull ArrayList data) { + dataSet = data; + notifyDataSetChanged(); + } + + public ArrayList getDataset() { + return dataSet; + } + + public class ViewHolder extends MediaEntryViewHolder { + + + @BindView(R.id.action_shuffle) + @Nullable + View shuffle; + + public ViewHolder(View itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/playlist/PlaylistAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/playlist/PlaylistAdapter.java new file mode 100755 index 00000000..7ae8e1a4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/playlist/PlaylistAdapter.java @@ -0,0 +1,300 @@ +package code.name.monkey.retromusic.ui.adapter.playlist; + +import android.graphics.Bitmap; +import android.graphics.PorterDuff; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.PopupMenu; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.bumptech.glide.Glide; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ExecutionException; + +import butterknife.ButterKnife; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.ClearSmartPlaylistDialog; +import code.name.monkey.retromusic.dialogs.DeletePlaylistDialog; +import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper; +import code.name.monkey.retromusic.helper.menu.SongsMenuHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.loaders.PlaylistSongsLoader; +import code.name.monkey.retromusic.model.AbsCustomPlaylist; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist; +import code.name.monkey.retromusic.ui.adapter.base.AbsMultiSelectAdapter; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import io.reactivex.Observable; + +/** + * Created by BlackFootSanji on 9/19/2016. + */ +public class PlaylistAdapter extends AbsMultiSelectAdapter { + + public static final String TAG = PlaylistAdapter.class.getSimpleName(); + + private static final int SMART_PLAYLIST = 0; + private static final int DEFAULT_PLAYLIST = 1; + + protected final AppCompatActivity activity; + protected ArrayList dataSet; + protected int itemLayoutRes; + private ArrayList mSongs = new ArrayList<>(); + + + public PlaylistAdapter(AppCompatActivity activity, ArrayList dataSet, + @LayoutRes int itemLayoutRes, @Nullable CabHolder cabHolder) { + super(activity, cabHolder, R.menu.menu_playlists_selection); + this.activity = activity; + this.dataSet = dataSet; + this.itemLayoutRes = itemLayoutRes; + setHasStableIds(true); + } + + public ArrayList getDataSet() { + return dataSet; + } + + public void swapDataSet(ArrayList dataSet) { + this.dataSet = dataSet; + notifyDataSetChanged(); + } + + @Override + public long getItemId(int position) { + return dataSet.get(position).id; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(activity) + .inflate(itemLayoutRes, parent, false); + return createViewHolder(view, viewType); + } + + protected ViewHolder createViewHolder(View view, int viewType) { + return new ViewHolder(view, viewType); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + /* if (getItemViewType(position) == SMART_PLAYLIST) { + if (holder.viewList != null) { + holder.viewList.get(0).setOnClickListener( + v -> NavigationUtil.goToPlaylistNew(activity, new HistoryPlaylist(activity))); + holder.viewList.get(1).setOnClickListener( + v -> NavigationUtil.goToPlaylistNew(activity, new LastAddedPlaylist(activity))); + holder.viewList.get(2).setOnClickListener( + v -> NavigationUtil.goToPlaylistNew(activity, new MyTopTracksPlaylist(activity))); + } + return; + }*/ + final Playlist playlist = dataSet.get(position); + ArrayList songs = getSongs(playlist); + holder.itemView.setActivated(isChecked(playlist)); + + if (holder.title != null) { + holder.title.setText(playlist.name); + } + if (holder.text != null) { + holder.text.setText(String.format(Locale.getDefault(), "%d Songs", songs.size())); + } + if (holder.image != null) { + holder.image.setImageResource(getIconRes(playlist)); + } + if (holder.getAdapterPosition() == getItemCount() - 1) { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.GONE); + } + } else { + if (holder.shortSeparator != null && !(dataSet.get(position) instanceof AbsSmartPlaylist)) { + holder.shortSeparator.setVisibility(View.VISIBLE); + } + } + } + + private int getIconRes(Playlist playlist) { + if (playlist instanceof AbsSmartPlaylist) { + return ((AbsSmartPlaylist) playlist).iconRes; + } + return MusicUtil.isFavoritePlaylist(activity, playlist) ? R.drawable.ic_favorite_white_24dp + : R.drawable.ic_playlist_play_white_24dp; + } + + @Override + public int getItemViewType(int position) { + return dataSet.get(position) instanceof AbsSmartPlaylist ? SMART_PLAYLIST : DEFAULT_PLAYLIST; + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + @Override + protected Playlist getIdentifier(int position) { + return dataSet.get(position); + } + + @Override + protected String getName(Playlist playlist) { + return playlist.name; + } + + @Override + protected void onMultipleItemAction(@NonNull MenuItem menuItem, + @NonNull ArrayList selection) { + switch (menuItem.getItemId()) { + case R.id.action_delete_playlist: + for (int i = 0; i < selection.size(); i++) { + Playlist playlist = selection.get(i); + if (playlist instanceof AbsSmartPlaylist) { + AbsSmartPlaylist absSmartPlaylist = (AbsSmartPlaylist) playlist; + ClearSmartPlaylistDialog.create(absSmartPlaylist) + .show(activity.getSupportFragmentManager(), + "CLEAR_PLAYLIST_" + absSmartPlaylist.name); + selection.remove(playlist); + i--; + } + } + if (selection.size() > 0) { + DeletePlaylistDialog.create(selection) + .show(activity.getSupportFragmentManager(), "DELETE_PLAYLIST"); + } + break; + default: + SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId()); + break; + } + } + + public ArrayList getSongs() { + return mSongs; + } + + public void setSongs(ArrayList songs) { + mSongs = songs; + } + + @NonNull + private ArrayList getSongList(@NonNull List playlists) { + final ArrayList songs = new ArrayList<>(); + for (Playlist playlist : playlists) { + if (playlist instanceof AbsCustomPlaylist) { + songs.addAll(((AbsCustomPlaylist) playlist).getSongs(activity).blockingFirst()); + //((AbsCustomPlaylist) playlist).getSongs(activity).subscribe(this::setSongs); + } else { + songs + .addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id).blockingFirst()); + } + } + return songs; + } + + @Nullable + private ArrayList getSongs(@NonNull Playlist playlist) { + final ArrayList songs = new ArrayList<>(); + if (playlist instanceof AbsSmartPlaylist) { + songs.addAll(((AbsSmartPlaylist) playlist).getSongs(activity).blockingFirst()); + } else { + songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id).blockingFirst()); + } + return songs; + } + + private Observable> loadBitmaps(@NonNull ArrayList songs) { + return Observable.create(e -> { + ArrayList bitmaps = new ArrayList(); + for (Song song : songs) { + try { + Bitmap bitmap = Glide.with(activity) + .load(RetroUtil.getAlbumArtUri(song.albumId)) + .asBitmap() + .into(500, 500) + .get(); + if (bitmap != null) { + Log.i(TAG, "loadBitmaps: has"); + bitmaps.add(bitmap); + } + if (bitmaps.size() == 4) { + break; + } + } catch (InterruptedException | ExecutionException ex) { + ex.printStackTrace(); + } + } + e.onNext(bitmaps); + e.onComplete(); + }); + } + + public class ViewHolder extends MediaEntryViewHolder { + public ViewHolder(@NonNull View itemView, int itemViewType) { + super(itemView); + ButterKnife.bind(this, itemView); + if (image != null) { + int iconPadding = activity.getResources() + .getDimensionPixelSize(R.dimen.list_item_image_icon_padding); + image.setPadding(iconPadding, iconPadding, iconPadding, iconPadding); + image.setColorFilter(ATHUtil.resolveColor(activity, R.attr.iconColor), + PorterDuff.Mode.SRC_IN); + } + if (menu != null) { + menu.setOnClickListener(view -> { + final Playlist playlist = dataSet.get(getAdapterPosition()); + final PopupMenu popupMenu = new PopupMenu(activity, view); + popupMenu.inflate(getItemViewType() == SMART_PLAYLIST ? R.menu.menu_item_smart_playlist + : R.menu.menu_item_playlist); + if (playlist instanceof LastAddedPlaylist) { + popupMenu.getMenu().findItem(R.id.action_clear_playlist).setVisible(false); + } + popupMenu.setOnMenuItemClickListener(item -> { + if (item.getItemId() == R.id.action_clear_playlist) { + if (playlist instanceof AbsSmartPlaylist) { + ClearSmartPlaylistDialog.create((AbsSmartPlaylist) playlist) + .show(activity.getSupportFragmentManager(), + "CLEAR_SMART_PLAYLIST_" + playlist.name); + return true; + } + } + return PlaylistMenuHelper.handleMenuClick( + activity, dataSet.get(getAdapterPosition()), item); + }); + popupMenu.show(); + }); + } + } + + @Override + public void onClick(View view) { + if (isInQuickSelectMode()) { + toggleChecked(getAdapterPosition()); + } else { + Playlist playlist = dataSet.get(getAdapterPosition()); + NavigationUtil.goToPlaylistNew(activity, playlist); + } + } + + @Override + public boolean onLongClick(View view) { + toggleChecked(getAdapterPosition()); + return true; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/AbsOffsetSongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/AbsOffsetSongAdapter.java new file mode 100644 index 00000000..bcbf6cc0 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/AbsOffsetSongAdapter.java @@ -0,0 +1,110 @@ +package code.name.monkey.retromusic.ui.adapter.song; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Song; + + +public abstract class AbsOffsetSongAdapter extends SongAdapter { + + protected static final int OFFSET_ITEM = 0; + protected static final int SONG = 1; + + public AbsOffsetSongAdapter(AppCompatActivity activity, ArrayList dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) { + super(activity, dataSet, itemLayoutRes, usePalette, cabHolder); + } + + public AbsOffsetSongAdapter(AppCompatActivity activity, ArrayList dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder, boolean showSectionName) { + super(activity, dataSet, itemLayoutRes, usePalette, cabHolder, showSectionName); + } + + @NonNull + @Override + public SongAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == OFFSET_ITEM) { + View view = LayoutInflater.from(activity).inflate(R.layout.item_list_single_row, parent, false); + return createViewHolder(view); + } + return super.onCreateViewHolder(parent, viewType); + } + + @Override + protected SongAdapter.ViewHolder createViewHolder(View view) { + return new AbsOffsetSongAdapter.ViewHolder(view); + } + + @Override + public long getItemId(int position) { + position--; + if (position < 0) return -2; + return super.getItemId(position); + } + + @Nullable + @Override + protected Song getIdentifier(int position) { + position--; + if (position < 0) return null; + return super.getIdentifier(position); + } + + @Override + public int getItemCount() { + int superItemCount = super.getItemCount(); + return superItemCount == 0 ? 0 : superItemCount + 1; + } + + @Override + public int getItemViewType(int position) { + return position == 0 ? OFFSET_ITEM : SONG; + } + + @NonNull + @Override + public String getSectionName(int position) { + position--; + if (position < 0) return ""; + return super.getSectionName(position); + } + + public class ViewHolder extends SongAdapter.ViewHolder { + + public ViewHolder(@NonNull View itemView) { + super(itemView); + } + + @Override + protected Song getSong() { + if (getItemViewType() == OFFSET_ITEM) + return Song.EMPTY_SONG; // could also return null, just to be safe return empty song + return dataSet.get(getAdapterPosition() - 1); + } + + @Override + public void onClick(View v) { + if (isInQuickSelectMode() && getItemViewType() != OFFSET_ITEM) { + toggleChecked(getAdapterPosition()); + } else { + MusicPlayerRemote.openQueue(dataSet, getAdapterPosition() - 1, true); + } + } + + @Override + public boolean onLongClick(View view) { + if (getItemViewType() == OFFSET_ITEM) return false; + toggleChecked(getAdapterPosition()); + return true; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/OrderablePlaylistSongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/OrderablePlaylistSongAdapter.java new file mode 100644 index 00000000..cadfc52f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/OrderablePlaylistSongAdapter.java @@ -0,0 +1,145 @@ +package code.name.monkey.retromusic.ui.adapter.song; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; +import android.view.View; + +import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter; +import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder; +import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange; +import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags; +import code.name.monkey.retromusic.model.PlaylistSong; +import code.name.monkey.retromusic.model.Song; + +import java.util.ArrayList; +import java.util.List; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.RemoveFromPlaylistDialog; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.util.ViewUtil; + + +@SuppressWarnings("unchecked") +public class OrderablePlaylistSongAdapter extends PlaylistSongAdapter + implements DraggableItemAdapter { + + public static final String TAG = OrderablePlaylistSongAdapter.class.getSimpleName(); + + private OnMoveItemListener onMoveItemListener; + + public OrderablePlaylistSongAdapter(@NonNull AppCompatActivity activity, + @NonNull ArrayList dataSet, + @LayoutRes int itemLayoutRes, + boolean usePalette, + @Nullable CabHolder cabHolder, + @Nullable OnMoveItemListener onMoveItemListener) { + super(activity, (ArrayList) (List) dataSet, itemLayoutRes, usePalette, cabHolder); + setMultiSelectMenuRes(R.menu.menu_playlists_songs_selection); + this.onMoveItemListener = onMoveItemListener; + } + + @Override + protected SongAdapter.ViewHolder createViewHolder(View view) { + return new ViewHolder(view); + } + + @Override + public long getItemId(int position) { + position--; + if (position < 0) return -2; + return ((ArrayList) (List) dataSet).get(position).idInPlayList; // important! + } + + @Override + protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList selection) { + switch (menuItem.getItemId()) { + case R.id.action_remove_from_playlist: + RemoveFromPlaylistDialog.create((ArrayList) (List) selection).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); + return; + } + super.onMultipleItemAction(menuItem, selection); + } + + @Override + public boolean onCheckCanStartDrag(ViewHolder holder, int position, int x, int y) { + return onMoveItemListener != null && position > 0 && + (ViewUtil.hitTest(holder.dragView, x, y) || ViewUtil.hitTest(holder.image, x, y)); + } + + @Override + public ItemDraggableRange onGetItemDraggableRange(ViewHolder holder, int position) { + return new ItemDraggableRange(1, dataSet.size()); + } + + @Override + public void onMoveItem(int fromPosition, int toPosition) { + if (onMoveItemListener != null && fromPosition != toPosition) { + onMoveItemListener.onMoveItem(fromPosition - 1, toPosition - 1); + } + } + + @Override + public boolean onCheckCanDrop(int draggingPosition, int dropPosition) { + return dropPosition > 0; + } + + @Override + public void onItemDragStarted(int position) { + notifyDataSetChanged(); + } + + @Override + public void onItemDragFinished(int fromPosition, int toPosition, boolean result) { + notifyDataSetChanged(); + } + + public interface OnMoveItemListener { + void onMoveItem(int fromPosition, int toPosition); + } + + public class ViewHolder extends PlaylistSongAdapter.ViewHolder implements DraggableItemViewHolder { + @DraggableItemStateFlags + private int mDragStateFlags; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + if (dragView != null) { + if (onMoveItemListener != null) { + dragView.setVisibility(View.VISIBLE); + } else { + dragView.setVisibility(View.GONE); + } + } + } + + @Override + protected int getSongMenuRes() { + return R.menu.menu_item_playlist_song; + } + + @Override + protected boolean onSongMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_remove_from_playlist: + RemoveFromPlaylistDialog.create((PlaylistSong) getSong()).show(activity.getSupportFragmentManager(), "REMOVE_FROM_PLAYLIST"); + return true; + } + return super.onSongMenuItemClick(item); + } + + @Override + @DraggableItemStateFlags + public int getDragStateFlags() { + return mDragStateFlags; + } + + @Override + public void setDragStateFlags(@DraggableItemStateFlags int flags) { + mDragStateFlags = flags; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/PlayingQueueAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/PlayingQueueAdapter.java new file mode 100644 index 00000000..027c3d7d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/PlayingQueueAdapter.java @@ -0,0 +1,209 @@ +package code.name.monkey.retromusic.ui.adapter.song; + +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; + +import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter; +import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder; +import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange; +import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.ViewUtil; + + +public class PlayingQueueAdapter extends SongAdapter implements + DraggableItemAdapter { + + private static final int HISTORY = 0; + private static final int CURRENT = 1; + private static final int UP_NEXT = 2; + + private int current; + + public PlayingQueueAdapter(AppCompatActivity activity, ArrayList dataSet, int current, + @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) { + super(activity, dataSet, itemLayoutRes, usePalette, cabHolder); + this.current = current; + } + + @Override + protected SongAdapter.ViewHolder createViewHolder(View view) { + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull SongAdapter.ViewHolder holder, int position) { + super.onBindViewHolder(holder, position); + if (holder.imageText != null) { + holder.imageText.setText(String.valueOf(position - current)); + } + if (holder.time != null) { + holder.time.setText(MusicUtil.getReadableDurationString(getDataSet().get(position).duration)); + } + if (holder.getItemViewType() == HISTORY || holder.getItemViewType() == CURRENT) { + setAlpha(holder, 0.5f); + } + if (usePalette) { + setColor(holder, Color.WHITE); + } + } + + private void setColor(SongAdapter.ViewHolder holder, int white) { + + if (holder.title != null) { + holder.title.setTextColor(white); + } + if (holder.text != null) { + holder.text.setTextColor(white); + } + if (holder.time != null) { + holder.time.setTextColor(white); + } + if (holder.imageText != null) { + holder.imageText.setTextColor(white); + } + if (holder.menu != null) { + ((ImageView) holder.menu).setColorFilter(white, PorterDuff.Mode.SRC_IN); + } + } + + @Override + public void usePalette(boolean color) { + super.usePalette(color); + usePalette = color; + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + if (position < current) { + return HISTORY; + } else if (position > current) { + return UP_NEXT; + } + return CURRENT; + } + + @Override + protected void loadAlbumCover(Song song, SongAdapter.ViewHolder holder) { + // We don't want to load it in this adapter + } + + public void swapDataSet(ArrayList dataSet, int position) { + this.dataSet = dataSet; + current = position; + notifyDataSetChanged(); + } + + public void setCurrent(int current) { + this.current = current; + notifyDataSetChanged(); + } + + private void setAlpha(SongAdapter.ViewHolder holder, float alpha) { + if (holder.image != null) { + holder.image.setAlpha(alpha); + } + if (holder.title != null) { + holder.title.setAlpha(alpha); + } + if (holder.text != null) { + holder.text.setAlpha(alpha); + } + if (holder.imageText != null) { + holder.imageText.setAlpha(alpha); + } + if (holder.paletteColorContainer != null) { + holder.paletteColorContainer.setAlpha(alpha); + } + } + + @Override + public boolean onCheckCanStartDrag(ViewHolder holder, int position, int x, int y) { + return ViewUtil.hitTest(holder.imageText, x, y) || (ViewUtil.hitTest(holder.dragView, x, y)); + } + + @Override + public ItemDraggableRange onGetItemDraggableRange(ViewHolder holder, int position) { + return null; + } + + @Override + public void onMoveItem(int fromPosition, int toPosition) { + MusicPlayerRemote.moveSong(fromPosition, toPosition); + } + + @Override + public boolean onCheckCanDrop(int draggingPosition, int dropPosition) { + return true; + } + + @Override + public void onItemDragStarted(int position) { + notifyDataSetChanged(); + } + + @Override + public void onItemDragFinished(int fromPosition, int toPosition, boolean result) { + notifyDataSetChanged(); + } + + public class ViewHolder extends SongAdapter.ViewHolder implements DraggableItemViewHolder { + + @DraggableItemStateFlags + private int mDragStateFlags; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + if (imageText != null) { + imageText.setVisibility(View.VISIBLE); + } + if (image != null) { + image.setVisibility(View.GONE); + } + if (dragView != null) { + dragView.setVisibility(View.VISIBLE); + } + } + + @Override + protected int getSongMenuRes() { + return R.menu.menu_item_playing_queue_song; + } + + @Override + protected boolean onSongMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_remove_from_playing_queue: + MusicPlayerRemote.removeFromQueue(getAdapterPosition()); + return true; + } + return super.onSongMenuItemClick(item); + } + + @Override + @DraggableItemStateFlags + public int getDragStateFlags() { + return mDragStateFlags; + } + + @Override + public void setDragStateFlags(@DraggableItemStateFlags int flags) { + mDragStateFlags = flags; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/PlaylistSongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/PlaylistSongAdapter.java new file mode 100644 index 00000000..eb2ae048 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/PlaylistSongAdapter.java @@ -0,0 +1,93 @@ +package code.name.monkey.retromusic.ui.adapter.song; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.Pair; +import android.support.v7.app.AppCompatActivity; +import android.view.MenuItem; +import android.view.View; + +import java.util.ArrayList; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; + + +public class PlaylistSongAdapter extends AbsOffsetSongAdapter { + + public static final String TAG = PlaylistSongAdapter.class.getSimpleName(); + + public PlaylistSongAdapter(AppCompatActivity activity, @NonNull ArrayList dataSet, @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) { + super(activity, dataSet, itemLayoutRes, usePalette, cabHolder, false); + setMultiSelectMenuRes(R.menu.menu_cannot_delete_single_songs_playlist_songs_selection); + } + + @Override + protected SongAdapter.ViewHolder createViewHolder(View view) { + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull final SongAdapter.ViewHolder holder, int position) { + if (holder.getItemViewType() == OFFSET_ITEM) { + int textColor = ThemeStore.textColorSecondary(activity); + if (holder.title != null) { + holder.title.setText(MusicUtil.getPlaylistInfoString(activity, dataSet)); + holder.title.setTextColor(textColor); + } + + + if (holder.text != null) { + holder.text.setVisibility(View.GONE); + } + if (holder.menu != null) { + holder.menu.setVisibility(View.GONE); + } + if (holder.image != null) { + final int padding = activity.getResources().getDimensionPixelSize(R.dimen.default_item_margin) / 2; + holder.image.setPadding(padding, padding, padding, padding); + holder.image.setColorFilter(textColor); + holder.image.setImageResource(R.drawable.ic_timer_white_24dp); + } + if (holder.dragView != null) { + holder.dragView.setVisibility(View.GONE); + } + if (holder.separator != null) { + holder.separator.setVisibility(View.VISIBLE); + } + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.GONE); + } + } else { + super.onBindViewHolder(holder, position - 1); + } + } + + public class ViewHolder extends AbsOffsetSongAdapter.ViewHolder { + public ViewHolder(@NonNull View itemView) { + super(itemView); + + } + + @Override + protected int getSongMenuRes() { + return R.menu.menu_item_cannot_delete_single_songs_playlist_song; + } + + @Override + protected boolean onSongMenuItemClick(MenuItem item) { + if (item.getItemId() == R.id.action_go_to_album) { + Pair[] albumPairs = new Pair[]{ + Pair.create(image, activity.getString(R.string.transition_album_art))}; + NavigationUtil.goToAlbum(activity, dataSet.get(getAdapterPosition() - 1).albumId, albumPairs); + return true; + } + return super.onSongMenuItemClick(item); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/ShuffleButtonSongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/ShuffleButtonSongAdapter.java new file mode 100644 index 00000000..b2fd96c4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/ShuffleButtonSongAdapter.java @@ -0,0 +1,78 @@ +package code.name.monkey.retromusic.ui.adapter.song; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Song; +import java.util.ArrayList; + + +public class ShuffleButtonSongAdapter extends AbsOffsetSongAdapter { + + public ShuffleButtonSongAdapter(AppCompatActivity activity, ArrayList dataSet, + @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) { + super(activity, dataSet, itemLayoutRes, usePalette, cabHolder); + } + + @Override + protected SongAdapter.ViewHolder createViewHolder(View view) { + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull final SongAdapter.ViewHolder holder, int position) { + if (holder.getItemViewType() == OFFSET_ITEM) { + int accentColor = ThemeStore.accentColor(activity); + if (holder.title != null) { + holder.title.setText(activity.getResources().getString(R.string.action_shuffle_all)); + holder.title.setTextColor(accentColor); + /*((GradientTextView) holder.title).setLinearGradient(ThemeStore.accentColor(activity), + PhonographColorUtil.getMatColor(activity, "A400"), GradientTextView.LG_VERTICAL); + */ + } + if (holder.text != null) { + holder.text.setVisibility(View.GONE); + } + if (holder.menu != null) { + holder.menu.setVisibility(View.GONE); + } + if (holder.image != null) { + final int padding = + activity.getResources().getDimensionPixelSize(R.dimen.default_item_margin) / 2; + holder.image.setPadding(padding, padding, padding, padding); + holder.image.setColorFilter(accentColor); + holder.image.setImageResource(R.drawable.ic_shuffle_white_24dp); + } + if (holder.separator != null) { + holder.separator.setVisibility(View.VISIBLE); + } + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.GONE); + } + } else { + super.onBindViewHolder(holder, position - 1); + } + } + + public class ViewHolder extends AbsOffsetSongAdapter.ViewHolder { + + public ViewHolder(@NonNull View itemView) { + super(itemView); + } + + @Override + public void onClick(View v) { + if (getItemViewType() == OFFSET_ITEM) { + MusicPlayerRemote.openAndShuffleQueue(dataSet, true); + return; + } + super.onClick(v); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/SimpleSongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/SimpleSongAdapter.java new file mode 100755 index 00000000..050e8427 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/SimpleSongAdapter.java @@ -0,0 +1,69 @@ +package code.name.monkey.retromusic.ui.adapter.song; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import java.util.ArrayList; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.util.MusicUtil; + +/** + * Created by Monkey D Luffy on 3/31/2016. + */ +public class SimpleSongAdapter extends SongAdapter { + + private int textColor; + + public SimpleSongAdapter(AppCompatActivity context, ArrayList songs, @LayoutRes int i) { + super(context, songs, i, false, null); + textColor = ThemeStore.textColorPrimary(context); + } + + public void swapDataSet(ArrayList arrayList) { + this.dataSet.clear(); + this.dataSet = arrayList; + notifyDataSetChanged(); + } + + @NonNull + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + super.onBindViewHolder(holder, position); + int fixedTrackNumber = MusicUtil.getFixedTrackNumber(dataSet.get(position).trackNumber); + + if (holder.imageText != null) { + holder.imageText.setText(fixedTrackNumber > 0 ? String.valueOf(fixedTrackNumber) : "-"); + holder.imageText.setTextColor(textColor); + } + + if (holder.time != null) { + holder.time.setText(MusicUtil.getReadableDurationString(dataSet.get(position).duration)); + holder.time.setTextColor(textColor); + } + if (holder.title != null) { + holder.title.setTextColor(textColor); + } + if (holder.menu != null) { + TintHelper.setTintAuto(holder.menu, textColor, false); + } + } + + public int getItemCount() { + return dataSet.size(); + } + + public void setTextColor(int textColor) { + this.textColor = textColor; + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/SongAdapter.java b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/SongAdapter.java new file mode 100644 index 00000000..99fadd57 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/adapter/song/SongAdapter.java @@ -0,0 +1,294 @@ +package code.name.monkey.retromusic.ui.adapter.song; + +import android.graphics.drawable.Drawable; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.Pair; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.afollestad.materialcab.MaterialCab; +import com.bumptech.glide.Glide; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import java.util.ArrayList; + +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.SortOrder; +import code.name.monkey.retromusic.helper.menu.SongMenuHelper; +import code.name.monkey.retromusic.helper.menu.SongsMenuHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.adapter.base.AbsMultiSelectAdapter; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * Created by hemanths on 13/08/17. + */ + +public class SongAdapter extends AbsMultiSelectAdapter + implements MaterialCab.Callback, FastScrollRecyclerView.SectionedAdapter { + + public static final String TAG = SongAdapter.class.getSimpleName(); + + protected final AppCompatActivity activity; + protected ArrayList dataSet; + + protected int itemLayoutRes; + + protected boolean usePalette = false; + private boolean showSectionName = true; + + + public SongAdapter(AppCompatActivity activity, ArrayList dataSet, + @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder) { + this(activity, dataSet, itemLayoutRes, usePalette, cabHolder, true); + } + + public SongAdapter(AppCompatActivity activity, ArrayList dataSet, + @LayoutRes int itemLayoutRes, boolean usePalette, @Nullable CabHolder cabHolder, + boolean showSectionName) { + super(activity, cabHolder, R.menu.menu_media_selection); + this.activity = activity; + this.dataSet = dataSet; + this.itemLayoutRes = itemLayoutRes; + this.usePalette = usePalette; + this.showSectionName = showSectionName; + setHasStableIds(true); + } + + public void swapDataSet(ArrayList dataSet) { + this.dataSet = dataSet; + notifyDataSetChanged(); + } + + public void usePalette(boolean usePalette) { + this.usePalette = usePalette; + notifyDataSetChanged(); + } + + public ArrayList getDataSet() { + return dataSet; + } + + @Override + public long getItemId(int position) { + return dataSet.get(position).id; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false); + return createViewHolder(view); + } + + protected ViewHolder createViewHolder(View view) { + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { + final Song song = dataSet.get(position); + + boolean isChecked = isChecked(song); + holder.itemView.setActivated(isChecked); + + if (holder.getAdapterPosition() == getItemCount() - 1) { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.GONE); + } + } else { + if (holder.shortSeparator != null) { + holder.shortSeparator.setVisibility(View.VISIBLE); + } + } + + if (holder.title != null) { + holder.title.setText(getSongTitle(song)); + } + if (holder.text != null) { + holder.text.setText(getSongText(song)); + } + + loadAlbumCover(song, holder); + + } + + private void setColors(int color, ViewHolder holder) { + if (holder.paletteColorContainer != null) { + holder.paletteColorContainer.setBackgroundColor(color); + if (holder.title != null) { + holder.title.setTextColor( + MaterialValueHelper.getPrimaryTextColor(activity, ColorUtil.isColorLight(color))); + } + if (holder.text != null) { + holder.text.setTextColor( + MaterialValueHelper.getSecondaryTextColor(activity, ColorUtil.isColorLight(color))); + } + } + } + + protected void loadAlbumCover(Song song, final ViewHolder holder) { + if (holder.image == null) { + return; + } + SongGlideRequest.Builder.from(Glide.with(activity), song) + .checkIgnoreMediaStore(activity) + .generatePalette(activity).build() + .into(new RetroMusicColoredTarget(holder.image) { + @Override + public void onLoadCleared(Drawable placeholder) { + super.onLoadCleared(placeholder); + setColors(getDefaultFooterColor(), holder); + } + + @Override + public void onColorReady(int color) { + setColors(color, holder); + } + }); + } + + private String getSongTitle(Song song) { + return song.title; + } + + private String getSongText(Song song) { + return song.artistName; + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + @Override + protected Song getIdentifier(int position) { + return dataSet.get(position); + } + + @Override + protected String getName(Song song) { + return song.title; + } + + @Override + protected void onMultipleItemAction(@NonNull MenuItem menuItem, + @NonNull ArrayList selection) { + SongsMenuHelper.handleMenuClick(activity, selection, menuItem.getItemId()); + } + + @NonNull + @Override + public String getSectionName(int position) { + if (!showSectionName) { + return ""; + } + @Nullable String sectionName = null; + switch (PreferenceUtil.getInstance(activity).getSongSortOrder()) { + case SortOrder.SongSortOrder.SONG_A_Z: + case SortOrder.SongSortOrder.SONG_Z_A: + sectionName = dataSet.get(position).title; + break; + case SortOrder.SongSortOrder.SONG_ALBUM: + sectionName = dataSet.get(position).albumName; + break; + case SortOrder.SongSortOrder.SONG_ARTIST: + sectionName = dataSet.get(position).artistName; + break; + case SortOrder.SongSortOrder.SONG_YEAR: + return MusicUtil.getYearString(dataSet.get(position).year); + } + + return MusicUtil.getSectionName(sectionName); + } + + public class ViewHolder extends MediaEntryViewHolder { + + int DEFAULT_MENU_RES = SongMenuHelper.MENU_RES; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + setImageTransitionName(activity.getString(R.string.transition_album_art)); + + /*if (mItemView != null) { + mItemView.setOnMenuItemClickListener(new ListItemView.OnMenuItemClickListener() { + @Override + public void onActionMenuItemSelected(MenuItem item) { + SongMenuHelper.handleMenuClick(activity, dataSet.get(getAdapterPosition()), item.getItemId()); + } + }); + }*/ + + if (menu == null) { + return; + } + menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu(activity) { + @Override + public Song getSong() { + return ViewHolder.this.getSong(); + } + + @Override + public int getMenuRes() { + return getSongMenuRes(); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + return onSongMenuItemClick(item) || super.onMenuItemClick(item); + } + }); + } + + protected Song getSong() { + return dataSet.get(getAdapterPosition()); + } + + protected int getSongMenuRes() { + return DEFAULT_MENU_RES; + } + + protected boolean onSongMenuItemClick(MenuItem item) { + if (image != null && image.getVisibility() == View.VISIBLE) { + switch (item.getItemId()) { + case R.id.action_go_to_album: + Pair[] albumPairs = new Pair[]{ + Pair.create(imageContainer, + activity.getResources().getString(R.string.transition_album_art)) + }; + NavigationUtil.goToAlbum(activity, getSong().albumId, albumPairs); + return true; + } + } + return false; + } + + @Override + public void onClick(View v) { + if (isInQuickSelectMode()) { + toggleChecked(getAdapterPosition()); + } else { + MusicPlayerRemote.openQueue(dataSet, getAdapterPosition(), true); + } + } + + @Override + public boolean onLongClick(View view) { + return toggleChecked(getAdapterPosition()); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/MiniPlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/MiniPlayerFragment.java new file mode 100644 index 00000000..6035247b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/MiniPlayerFragment.java @@ -0,0 +1,176 @@ +package code.name.monkey.retromusic.ui.fragments; + +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.ui.fragments.base.AbsMusicServiceFragment; +import code.name.monkey.retromusic.views.PlayPauseDrawable; +import me.zhanghai.android.materialprogressbar.MaterialProgressBar; + +public class MiniPlayerFragment extends AbsMusicServiceFragment implements + MusicProgressViewUpdateHelper.Callback { + + @BindView(R.id.mini_player_title) + TextView miniPlayerTitle; + @BindView(R.id.mini_player_play_pause_button) + ImageView miniPlayerPlayPauseButton; + @BindView(R.id.progress_bar) + MaterialProgressBar progressBar; + + private Unbinder unbinder; + private PlayPauseDrawable miniPlayerPlayPauseDrawable; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.fragment_mini_player, container, false); + unbinder = ButterKnife.bind(this, layout); + return layout; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + view.setBackgroundColor(ThemeStore.primaryColor(getContext())); + view.setOnTouchListener(new FlingPlayBackController(getActivity())); + setUpMiniPlayer(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @SuppressWarnings({"ConstantConditions"}) + private void setUpMiniPlayer() { + setUpPlayPauseButton(); + progressBar.setProgressTintList(ColorStateList.valueOf(ThemeStore.accentColor(getActivity()))); + } + + private void setUpPlayPauseButton() { + //noinspection ConstantConditions + miniPlayerPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + miniPlayerPlayPauseButton.setImageDrawable(miniPlayerPlayPauseDrawable); + miniPlayerPlayPauseButton.setColorFilter(ATHUtil.resolveColor(getActivity(), + R.attr.iconColor, + ThemeStore.textColorSecondary(getActivity())), PorterDuff.Mode.SRC_IN); + miniPlayerPlayPauseButton.setOnClickListener(new PlayPauseButtonOnClickHandler()); + } + + private void updateSongTitle() { + miniPlayerTitle.setText(MusicPlayerRemote.getCurrentSong().title); + } + + @Override + public void onServiceConnected() { + updateSongTitle(); + updatePlayPauseDrawableState(false); + } + + @Override + public void onPlayingMetaChanged() { + updateSongTitle(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressBar.setMax(total); + ObjectAnimator animator = ObjectAnimator.ofInt(progressBar, "progress", progress); + animator.setDuration(1000); + animator.setInterpolator(new DecelerateInterpolator()); + animator.start(); + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + miniPlayerPlayPauseDrawable.setPause(animate); + } else { + miniPlayerPlayPauseDrawable.setPlay(animate); + } + } + + public void setColor(int playerFragmentColor) { + //noinspection ConstantConditions + getView().setBackgroundColor(playerFragmentColor); + } + + public static class FlingPlayBackController implements View.OnTouchListener { + + GestureDetector flingPlayBackController; + + public FlingPlayBackController(Context context) { + flingPlayBackController = new GestureDetector(context, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (Math.abs(velocityX) > Math.abs(velocityY)) { + if (velocityX < 0) { + MusicPlayerRemote.playNextSong(); + return true; + } else if (velocityX > 0) { + MusicPlayerRemote.playPreviousSong(); + return true; + } + } + return false; + } + }); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View v, MotionEvent event) { + return flingPlayBackController.onTouchEvent(event); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/NowPlayingScreen.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/NowPlayingScreen.java new file mode 100644 index 00000000..77483265 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/NowPlayingScreen.java @@ -0,0 +1,34 @@ +package code.name.monkey.retromusic.ui.fragments; + +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; + +import code.name.monkey.retromusic.R; + + +public enum NowPlayingScreen { + NORMAL(R.string.normal, R.drawable.np_normal, 0), + FLAT(R.string.flat, R.drawable.np_flat, 1), + FULL(R.string.full, R.drawable.np_full, 2), + PLAIN(R.string.plain, R.drawable.np_plain, 3), + BLUR(R.string.blur, R.drawable.np_blur, 4), + COLOR(R.string.color, R.drawable.np_color, 5), + CARD(R.string.card, R.drawable.np_card, 6), + TINY(R.string.tiny, R.drawable.np_tiny, 7), + SIMPLE(R.string.simple, R.drawable.np_simple, 8), + BLUR_CARD(R.string.blur_card, R.drawable.np_blur_card, 9), + ADAPTIVE(R.string.adaptive, R.drawable.np_adaptive, 10), + MATERIAL(R.string.material, R.drawable.np_normal, 11); + + @StringRes + public final int titleRes; + @DrawableRes + public final int drawableResId; + public final int id; + + NowPlayingScreen(@StringRes int titleRes, @DrawableRes int drawableResId, int id) { + this.titleRes = titleRes; + this.drawableResId = drawableResId; + this.id = id; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/PlayingQueueFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/PlayingQueueFragment.java new file mode 100644 index 00000000..6dd59ae5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/PlayingQueueFragment.java @@ -0,0 +1,143 @@ +package code.name.monkey.retromusic.ui.fragments; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.ui.adapter.song.PlayingQueueAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsMusicServiceFragment; +import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager; +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; + +public class PlayingQueueFragment extends AbsMusicServiceFragment { + + @BindView(R.id.recycler_view) + RecyclerView mRecyclerView; + Unbinder unbinder; + private RecyclerView.Adapter mWrappedAdapter; + private RecyclerViewDragDropManager mRecyclerViewDragDropManager; + private PlayingQueueAdapter mPlayingQueueAdapter; + private LinearLayoutManager mLayoutManager; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_main_activity_recycler_view, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpRecyclerView(); + } + + private void setUpRecyclerView() { + mRecyclerViewDragDropManager = new RecyclerViewDragDropManager(); + final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator(); + + mPlayingQueueAdapter = new PlayingQueueAdapter( + (AppCompatActivity) getActivity(), + MusicPlayerRemote.getPlayingQueue(), + MusicPlayerRemote.getPosition(), + R.layout.item_list, + false, + null); + mWrappedAdapter = mRecyclerViewDragDropManager.createWrappedAdapter(mPlayingQueueAdapter); + + mLayoutManager = new LinearLayoutManager(getContext()); + + mRecyclerView.setLayoutManager(mLayoutManager); + mRecyclerView.setAdapter(mWrappedAdapter); + mRecyclerView.setItemAnimator(animator); + mRecyclerViewDragDropManager.attachRecyclerView(mRecyclerView); + mLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0); + } + + @Override + public void onQueueChanged() { + updateQueue(); + updateCurrentSong(); + } + + @Override + public void onMediaStoreChanged() { + updateQueue(); + updateCurrentSong(); + } + + @SuppressWarnings("ConstantConditions") + private void updateCurrentSong() { + } + + @Override + public void onPlayingMetaChanged() { + //updateCurrentSong(); + //updateIsFavorite(); + updateQueuePosition(); + //updateLyrics(); + } + + private void updateQueuePosition() { + mPlayingQueueAdapter.setCurrent(MusicPlayerRemote.getPosition()); + // if (slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { + resetToCurrentPosition(); + //} + } + + private void updateQueue() { + mPlayingQueueAdapter + .swapDataSet(MusicPlayerRemote.getPlayingQueue(), MusicPlayerRemote.getPosition()); + resetToCurrentPosition(); + } + + private void resetToCurrentPosition() { + mRecyclerView.stopScroll(); + mLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0); + } + + @Override + public void onPause() { + if (mRecyclerViewDragDropManager != null) { + mRecyclerViewDragDropManager.cancelDrag(); + } + super.onPause(); + } + + @Override + public void onDestroyView() { + if (mRecyclerViewDragDropManager != null) { + mRecyclerViewDragDropManager.release(); + mRecyclerViewDragDropManager = null; + } + + if (mRecyclerView != null) { + mRecyclerView.setItemAnimator(null); + mRecyclerView.setAdapter(null); + mRecyclerView = null; + } + + if (mWrappedAdapter != null) { + WrapperAdapterUtils.releaseAll(mWrappedAdapter); + mWrappedAdapter = null; + } + mPlayingQueueAdapter = null; + mLayoutManager = null; + super.onDestroyView(); + unbinder.unbind(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/VolumeFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/VolumeFragment.java new file mode 100755 index 00000000..f9a4e927 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/VolumeFragment.java @@ -0,0 +1,158 @@ +package code.name.monkey.retromusic.ui.fragments; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.media.AudioManager; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.SeekBar; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.volume.AudioVolumeObserver; +import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener; + +public class VolumeFragment extends Fragment implements SeekBar.OnSeekBarChangeListener, + OnAudioVolumeChangedListener { + + @BindView(R.id.volume_seekbar) + SeekBar volumeSeekbar; + @BindView(R.id.volume_down) + ImageView volumeDown; + @BindView(R.id.container) + ViewGroup viewGroup; + @BindView(R.id.volume_up) + ImageView volumeUp; + + private Unbinder unbinder; + private AudioVolumeObserver mAudioVolumeObserver; + + public static VolumeFragment newInstance() { + return new VolumeFragment(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_volume, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + //noinspection ConstantConditions + setColor(ThemeStore.textColorSecondary(getContext())); + } + + @Override + public void onResume() { + super.onResume(); + if (mAudioVolumeObserver == null) { + //noinspection ConstantConditions + mAudioVolumeObserver = new AudioVolumeObserver(getActivity()); + } + mAudioVolumeObserver.register(AudioManager.STREAM_MUSIC, this); + + AudioManager audioManager = getAudioManager(); + if (audioManager != null) { + volumeSeekbar.setMax(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)); + volumeSeekbar.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)); + } + volumeSeekbar.setOnSeekBarChangeListener(this); + } + + @Override + public void onAudioVolumeChanged(int currentVolume, int maxVolume) { + if (volumeSeekbar == null) { + return; + } + volumeSeekbar.setMax(maxVolume); + volumeSeekbar.setProgress(currentVolume); + volumeDown.setImageResource(currentVolume == 0 ? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_down_white_24dp); + } + + private AudioManager getAudioManager() { + //noinspection ConstantConditions + return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + if (mAudioVolumeObserver != null) { + mAudioVolumeObserver.unregister(); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + AudioManager audioManager = getAudioManager(); + if (audioManager != null) { + audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, i, 0); + } + volumeDown.setImageResource( + i == 0 ? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_down_white_24dp); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + + @OnClick({R.id.volume_down, R.id.volume_up}) + public void onViewClicked(View view) { + AudioManager audioManager = getAudioManager(); + switch (view.getId()) { + case R.id.volume_down: + if (audioManager != null) { + audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0); + } + break; + case R.id.volume_up: + if (audioManager != null) { + audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, 0); + } + break; + } + } + + public void setColor(int color) { + volumeSeekbar.setProgressTintList(ColorStateList.valueOf(color)); + } + + public void tintWhiteColor() { + setProgressBarColor(Color.WHITE); + } + + public void setProgressBarColor(int newColor) { + TintHelper.setTintAuto(volumeSeekbar, newColor, false); + volumeDown.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); + volumeUp.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); + } + + public void setTintable(int color) { + setProgressBarColor(color); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerFragment.java new file mode 100644 index 00000000..5871de85 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerFragment.java @@ -0,0 +1,20 @@ +package code.name.monkey.retromusic.ui.fragments.base; + +import android.os.Bundle; +import android.support.v4.app.LoaderManager; + +import code.name.monkey.retromusic.ui.fragments.mainactivity.LibraryFragment; + +public class AbsLibraryPagerFragment extends AbsMusicServiceFragment { + + + public LibraryFragment getLibraryFragment() { + return (LibraryFragment) getParentFragment(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerRecyclerViewCustomGridSizeFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerRecyclerViewCustomGridSizeFragment.java new file mode 100644 index 00000000..16f999ef --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerRecyclerViewCustomGridSizeFragment.java @@ -0,0 +1,169 @@ +package code.name.monkey.retromusic.ui.fragments.base; + +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.RetroUtil; + + +public abstract class AbsLibraryPagerRecyclerViewCustomGridSizeFragment extends + AbsLibraryPagerRecyclerViewFragment { + + private int gridSize; + private String sortOrder; + + private boolean usePaletteInitialized; + private boolean usePalette; + private int currentLayoutRes; + + public final int getGridSize() { + if (gridSize == 0) { + if (isLandscape()) { + gridSize = loadGridSizeLand(); + } else { + gridSize = loadGridSize(); + } + } + return gridSize; + } + + protected abstract void setGridSize(int gridSize); + + public final String getSortOrder() { + if (sortOrder == null) { + sortOrder = loadSortOrder(); + } + return sortOrder; + } + + protected abstract void setSortOrder(String sortOrder); + + public void setAndSaveSortOrder(final String sortOrder) { + this.sortOrder = sortOrder; + saveSortOrder(sortOrder); + setSortOrder(sortOrder); + } + + public int getMaxGridSize() { + if (isLandscape()) { + return getResources().getInteger(R.integer.max_columns_land); + } else { + return getResources().getInteger(R.integer.max_columns); + } + } + + /** + * @return whether the palette should be used at all or not + */ + public final boolean usePalette() { + if (!usePaletteInitialized) { + usePalette = loadUsePalette(); + usePaletteInitialized = true; + } + return usePalette; + } + + public void setAndSaveGridSize(final int gridSize) { + int oldLayoutRes = getItemLayoutRes(); + this.gridSize = gridSize; + if (isLandscape()) { + saveGridSizeLand(gridSize); + } else { + saveGridSize(gridSize); + } + // only recreate the adapter and layout manager if the layout currentLayoutRes has changed + if (oldLayoutRes != getItemLayoutRes()) { + invalidateLayoutManager(); + invalidateAdapter(); + } else { + setGridSize(gridSize); + } + } + + public void setAndSaveUsePalette(final boolean usePalette) { + this.usePalette = usePalette; + saveUsePalette(usePalette); + setUsePalette(usePalette); + } + + /** + * @return whether the palette option should be available for the current item layout or not + */ + public boolean canUsePalette() { + return getItemLayoutRes() == R.layout.item_color; + } + + /** + * Override to customize which item layout currentLayoutRes should be used. You might also want to + * override {@link #canUsePalette()} then. + * + * @see #getGridSize() + */ + @LayoutRes + protected int getItemLayoutRes() { + if (getGridSize() > getMaxGridSizeForList()) { + return R.layout.item_grid; + } + return R.layout.item_list; + } + + protected final void notifyLayoutResChanged(@LayoutRes int res) { + this.currentLayoutRes = res; + RecyclerView recyclerView = getRecyclerView(); + if (recyclerView != null) { + applyRecyclerViewPaddingForLayoutRes(recyclerView, currentLayoutRes); + } + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + applyRecyclerViewPaddingForLayoutRes(getRecyclerView(), currentLayoutRes); + } + + protected void applyRecyclerViewPaddingForLayoutRes(@NonNull RecyclerView recyclerView, + @LayoutRes int res) { + int padding; + if (res == R.layout.item_grid) { + padding = (int) (getResources().getDisplayMetrics().density * 2); + } else { + padding = 0; + } + recyclerView.setPadding(padding, padding, padding, padding); + } + + protected abstract String loadSortOrder(); + + protected abstract void saveSortOrder(String sortOrder); + + protected abstract int loadGridSize(); + + protected abstract void saveGridSize(int gridColumns); + + protected abstract int loadGridSizeLand(); + + protected abstract void saveGridSizeLand(int gridColumns); + + protected abstract void saveUsePalette(boolean usePalette); + + protected abstract boolean loadUsePalette(); + + protected abstract void setUsePalette(boolean usePalette); + + protected int getMaxGridSizeForList() { + if (isLandscape()) { + return getActivity().getResources().getInteger(R.integer.default_list_columns_land); + } + return getActivity().getResources().getInteger(R.integer.default_list_columns); + } + + protected final boolean isLandscape() { + return RetroUtil.isLandscape(getResources()); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerRecyclerViewFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerRecyclerViewFragment.java new file mode 100644 index 00000000..76210cf9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsLibraryPagerRecyclerViewFragment.java @@ -0,0 +1,162 @@ +package code.name.monkey.retromusic.ui.fragments.base; + +import android.os.Bundle; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.AppBarLayout.OnOffsetChangedListener; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.util.ViewUtil; + + +public abstract class AbsLibraryPagerRecyclerViewFragment extends + AbsLibraryPagerFragment implements OnOffsetChangedListener { + + public static final String TAG = AbsLibraryPagerRecyclerViewFragment.class.getSimpleName(); + @BindView(R.id.container) + ViewGroup container; + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + @BindView(android.R.id.empty) + TextView empty; + + private Unbinder unbinder; + private A adapter; + private LM layoutManager; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(getLayoutRes(), container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + getLibraryFragment().addOnAppBarOffsetChangedListener(this); + initLayoutManager(); + initAdapter(); + setUpRecyclerView(); + } + + private void setUpRecyclerView() { + if (recyclerView instanceof FastScrollRecyclerView) { + //noinspection ConstantConditions + ViewUtil.setUpFastScrollRecyclerViewColor(getActivity(), + ((FastScrollRecyclerView) recyclerView), ThemeStore.accentColor(getActivity())); + } + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(adapter); + } + + @Override + public void onQueueChanged() { + super.onQueueChanged(); + checkForPadding(); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + checkForPadding(); + } + + private void checkForPadding() { + int height = (MusicPlayerRemote.getPlayingQueue().isEmpty() ? getResources().getDimensionPixelSize(R.dimen.mini_player_height) : 0); + recyclerView.setPadding(0, 0, 0, height); + } + + protected void invalidateLayoutManager() { + initLayoutManager(); + recyclerView.setLayoutManager(layoutManager); + } + + protected void invalidateAdapter() { + initAdapter(); + checkIsEmpty(); + recyclerView.setAdapter(adapter); + } + + private void initAdapter() { + adapter = createAdapter(); + adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + checkForPadding(); + } + }); + } + + private void initLayoutManager() { + layoutManager = createLayoutManager(); + } + + protected A getAdapter() { + return adapter; + } + + protected LM getLayoutManager() { + return layoutManager; + } + + protected RecyclerView getRecyclerView() { + return recyclerView; + } + + public ViewGroup getContainer() { + return container; + } + + @Override + public void onOffsetChanged(AppBarLayout appBarLayout, int i) { + container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), + container.getPaddingRight(), getLibraryFragment().getTotalAppBarScrollingRange() + i); + } + + private void checkIsEmpty() { + empty.setText(getEmptyMessage()); + empty.setVisibility(adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + + @StringRes + protected int getEmptyMessage() { + return R.string.empty; + } + + @LayoutRes + protected int getLayoutRes() { + return R.layout.fragment_main_activity_recycler_view; + } + + protected abstract LM createLayoutManager(); + + @NonNull + protected abstract A createAdapter(); + + @Override + public void onDestroyView() { + super.onDestroyView(); + getLibraryFragment().removeOnAppBarOffsetChangedListener(this); + unbinder.unbind(); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsMainActivityFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsMainActivityFragment.java new file mode 100644 index 00000000..8d82a715 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsMainActivityFragment.java @@ -0,0 +1,52 @@ +package code.name.monkey.retromusic.ui.fragments.base; + +import android.os.Build; +import android.os.Bundle; +import android.view.View; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.util.RetroUtil; + + +public abstract class AbsMainActivityFragment extends AbsMusicServiceFragment { + + public MainActivity getMainActivity() { + return (MainActivity) getActivity(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + + getMainActivity().setNavigationbarColorAuto(); + getMainActivity().setLightNavigationBar(true); + getMainActivity().setTaskDescriptionColorAuto(); + getMainActivity().hideStatusBar(); + } + + + // WORKAROUND + public void setStatusbarColor(View view, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + final View statusBar = view.findViewById(R.id.status_bar); + if (statusBar != null) { + RetroUtil.statusBarHeight(statusBar); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + statusBar.setBackgroundColor(ColorUtil.darkenColor(color)); + getMainActivity().setLightStatusbarAuto(color); + } else { + statusBar.setBackgroundColor(color); + } + } + } + } + + public void setStatusbarColorAuto(View view) { + // we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat + setStatusbarColor(view, ThemeStore.primaryColor(getContext())); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsMusicServiceFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsMusicServiceFragment.java new file mode 100644 index 00000000..5d8ee787 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsMusicServiceFragment.java @@ -0,0 +1,89 @@ +package code.name.monkey.retromusic.ui.fragments.base; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.view.View; + +import code.name.monkey.retromusic.interfaces.MusicServiceEventListener; + +import code.name.monkey.retromusic.ui.activities.base.AbsMusicServiceActivity; + +/** + * Created by hemanths on 18/08/17. + */ + +public class AbsMusicServiceFragment extends Fragment implements MusicServiceEventListener { + + private AbsMusicServiceActivity activity; + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + activity = (AbsMusicServiceActivity) context; + } catch (ClassCastException e) { + throw new RuntimeException(context.getClass().getSimpleName() + " must be an instance of " + AbsMusicServiceActivity.class.getSimpleName()); + } + } + + @Override + public void onDetach() { + super.onDetach(); + activity = null; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + activity.addMusicServiceEventListener(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + activity.removeMusicServiceEventListener(this); + } + + @Override + public void onPlayingMetaChanged() { + + } + + @Override + public void onServiceConnected() { + + } + + @Override + public void onServiceDisconnected() { + + } + + @Override + public void onQueueChanged() { + + } + + @Override + public void onPlayStateChanged() { + + } + + @Override + public void onRepeatModeChanged() { + + } + + @Override + public void onShuffleModeChanged() { + + } + + @Override + public void onMediaStoreChanged() { + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerControlsFragment.java new file mode 100644 index 00000000..d5a8d090 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerControlsFragment.java @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic.ui.fragments.base; + +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; + +/** + * Created by hemanths on 24/09/17. + */ + +public abstract class AbsPlayerControlsFragment extends AbsMusicServiceFragment + implements MusicProgressViewUpdateHelper.Callback { + + protected abstract void show(); + + protected abstract void hide(); + + protected abstract void updateShuffleState(); + + protected abstract void updateRepeatState(); + + protected abstract void setUpProgressSlider(); + + public abstract void setDark(int color); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerFragment.java new file mode 100644 index 00000000..cd694d76 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/base/AbsPlayerFragment.java @@ -0,0 +1,282 @@ +package code.name.monkey.retromusic.ui.fragments.base; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog; +import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog; +import code.name.monkey.retromusic.dialogs.DeleteSongsDialog; +import code.name.monkey.retromusic.dialogs.SleepTimerDialog; +import code.name.monkey.retromusic.dialogs.SongDetailDialog; +import code.name.monkey.retromusic.dialogs.SongShareDialog; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.interfaces.PaletteColorHolder; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.activities.tageditor.AbsTagEditorActivity; +import code.name.monkey.retromusic.ui.activities.tageditor.SongTagEditorActivity; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; + +public abstract class AbsPlayerFragment extends AbsMusicServiceFragment implements + Toolbar.OnMenuItemClickListener, PaletteColorHolder { + + public static final String TAG = AbsPlayerFragment.class.getSimpleName(); + private boolean isToolbarShown = true; + private Callbacks callbacks; + private AsyncTask updateIsFavoriteTask; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + callbacks = (Callbacks) context; + } catch (ClassCastException e) { + throw new RuntimeException( + context.getClass().getSimpleName() + " must implement " + Callbacks.class + .getSimpleName()); + } + } + + @Override + public void onDetach() { + super.onDetach(); + callbacks = null; + } + + @SuppressWarnings("ConstantConditions") + @Override + public boolean onMenuItemClick(MenuItem item) { + final Song song = MusicPlayerRemote.getCurrentSong(); + switch (item.getItemId()) { + case R.id.action_toggle_favorite: + toggleFavorite(song); + return true; + case R.id.action_share: + if (getFragmentManager() != null) { + SongShareDialog.create(song).show(getFragmentManager(), "SHARE_SONG"); + } + return true; + case R.id.action_delete_from_device: + DeleteSongsDialog.create(song) + .show(getActivity().getSupportFragmentManager(), "DELETE_SONGS"); + return true; + case R.id.action_add_to_playlist: + if (getFragmentManager() != null) { + AddToPlaylistDialog.create(song).show(getFragmentManager(), "ADD_PLAYLIST"); + } + return true; + case R.id.action_clear_playing_queue: + MusicPlayerRemote.clearQueue(); + return true; + case R.id.action_save_playing_queue: + CreatePlaylistDialog.create(MusicPlayerRemote.getPlayingQueue()) + .show(getActivity().getSupportFragmentManager(), "ADD_TO_PLAYLIST"); + return true; + case R.id.action_tag_editor: + Intent intent = new Intent(getActivity(), SongTagEditorActivity.class); + intent.putExtra(AbsTagEditorActivity.EXTRA_ID, song.id); + startActivity(intent); + return true; + case R.id.action_details: + if (getFragmentManager() != null) { + SongDetailDialog.create(song).show(getFragmentManager(), "SONG_DETAIL"); + } + return true; + case R.id.action_go_to_album: + NavigationUtil.goToAlbum(getActivity(), song.albumId); + return true; + case R.id.action_go_to_artist: + NavigationUtil.goToArtist(getActivity(), song.artistId); + return true; + case R.id.now_playing: + NavigationUtil.goToPlayingQueue(getActivity()); + return true; + case R.id.action_show_lyrics: + NavigationUtil.goToLyrics(getActivity()); + return true; + case R.id.action_equalizer: + NavigationUtil.openEqualizer(getActivity()); + return true; + case R.id.action_sleep_timer: + new SleepTimerDialog().show(getFragmentManager(), TAG); + return true; + case R.id.action_set_as_ringtone: + MusicUtil.setRingtone(getActivity(), song.id); + return true; + case R.id.action_go_to_genre: + /*MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + Uri trackUri = ContentUris + .withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, song.id); + retriever.setDataSource(getActivity(), trackUri); + String genre = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE); + if (genre == null) { + genre = "Not Specified"; + }*/ + Toast.makeText(getContext(), "Soon", Toast.LENGTH_SHORT).show(); + return true; + } + return false; + } + + protected void toggleFavorite(Song song) { + MusicUtil.toggleFavorite(getActivity(), song); + } + + protected boolean isToolbarShown() { + return isToolbarShown; + } + + protected void setToolbarShown(boolean toolbarShown) { + isToolbarShown = toolbarShown; + } + + protected void showToolbar(@Nullable final View toolbar) { + if (toolbar == null) { + return; + } + setToolbarShown(true); + toolbar.setVisibility(View.VISIBLE); + toolbar.animate().alpha(1f).setDuration(PlayerAlbumCoverFragment.VISIBILITY_ANIM_DURATION); + } + + protected void hideToolbar(@Nullable final View toolbar) { + if (toolbar == null) { + return; + } + + setToolbarShown(false); + + toolbar.animate().alpha(0f).setDuration(PlayerAlbumCoverFragment.VISIBILITY_ANIM_DURATION) + .withEndAction(new Runnable() { + @Override + public void run() { + toolbar.setVisibility(View.GONE); + } + }); + } + + protected void toggleToolbar(@Nullable final View toolbar) { + if (isToolbarShown()) { + hideToolbar(toolbar); + } else { + showToolbar(toolbar); + } + } + + protected void checkToggleToolbar(@Nullable final View toolbar) { + if (toolbar != null && !isToolbarShown() && toolbar.getVisibility() != View.GONE) { + hideToolbar(toolbar); + } else if (toolbar != null && isToolbarShown() && toolbar.getVisibility() != View.VISIBLE) { + showToolbar(toolbar); + } + } + + public abstract void onShow(); + + public abstract void onHide(); + + public abstract boolean onBackPressed(); + + public abstract Toolbar getToolbar(); + + public abstract int toolbarIconColor(); + + @Override + public void onServiceConnected() { + updateIsFavorite(); + //updateLyrics(); + } + + @Override + public void onPlayingMetaChanged() { + updateIsFavorite(); + //updateLyrics(); + } + + @Override + public void onDestroyView() { + + if (updateIsFavoriteTask != null && !updateIsFavoriteTask.isCancelled()) { + updateIsFavoriteTask.cancel(true); + } + super.onDestroyView(); + + } + + @SuppressLint("StaticFieldLeak") + public void updateIsFavorite() { + if (updateIsFavoriteTask != null) { + updateIsFavoriteTask.cancel(false); + } + updateIsFavoriteTask = new AsyncTask() { + @Override + protected Boolean doInBackground(Song... params) { + Activity activity = getActivity(); + if (activity != null) { + return MusicUtil.isFavorite(getActivity(), params[0]); + } else { + cancel(false); + return null; + } + } + + @Override + protected void onPostExecute(Boolean isFavorite) { + Activity activity = getActivity(); + if (activity != null) { + int res = isFavorite ? R.drawable.ic_favorite_white_24dp + : R.drawable.ic_favorite_border_white_24dp; + Drawable drawable = RetroUtil.getTintedVectorDrawable(activity, res, toolbarIconColor()); + getToolbar().getMenu().findItem(R.id.action_toggle_favorite) + .setIcon(drawable) + .setTitle(isFavorite ? getString(R.string.action_remove_from_favorites) + : getString(R.string.action_add_to_favorites)); + } + } + }.execute(MusicPlayerRemote.getCurrentSong()); + } + + public Callbacks getCallbacks() { + return callbacks; + } + + protected void toggleStatusBar(ViewGroup viewGroup) { + if (!PreferenceUtil.getInstance(getContext()).getFullScreenMode()) { + RetroUtil.statusBarHeight(viewGroup); + } + } + + protected void toggleStatusBar(View viewGroup) { + if (!PreferenceUtil.getInstance(getContext()).getFullScreenMode()) { + RetroUtil.statusBarHeight(viewGroup); + } + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + view.setBackgroundColor(ThemeStore.primaryColor(getActivity())); + } + + public interface Callbacks { + + void onPaletteColorChanged(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/intro/NameFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/intro/NameFragment.java new file mode 100644 index 00000000..e29e1e25 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/intro/NameFragment.java @@ -0,0 +1,275 @@ +package code.name.monkey.retromusic.ui.fragments.intro; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore.Images.Media; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Toast; + +import com.afollestad.materialdialogs.MaterialDialog.Builder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.Compressor; +import code.name.monkey.retromusic.util.ImageUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.CircularImageView; +import code.name.monkey.retromusic.views.IconImageView; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + +import static android.app.Activity.RESULT_OK; +import static code.name.monkey.retromusic.Constants.USER_BANNER; +import static code.name.monkey.retromusic.Constants.USER_PROFILE; + +public class NameFragment extends Fragment { + + private static final int PICK_IMAGE_REQUEST = 9002; + private static final int PICK_BANNER_REQUEST = 9003; + private static final int PROFILE_ICON_SIZE = 400; + @BindView(R.id.name) + EditText name; + @BindView(R.id.user_image) + CircularImageView userImage; + @BindView(R.id.image) + ImageView image; + @BindView(R.id.banner_select) + IconImageView imageView; + private Unbinder unbinder; + private CompositeDisposable disposable; + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + Bundle savedInstanceState) { + View view = LayoutInflater.from(getActivity()) + .inflate(R.layout.fragment_name, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + //noinspection ConstantConditions + name.setText(PreferenceUtil.getInstance(getActivity()).getUserName()); + if (!PreferenceUtil.getInstance(getActivity()).getProfileImage().isEmpty()) { + loadImageFromStorage(PreferenceUtil.getInstance(getActivity()).getProfileImage()); + } + if (!PreferenceUtil.getInstance(getActivity()).getBannerImage().isEmpty()) { + loadBannerFromStorage(PreferenceUtil.getInstance(getActivity()).getBannerImage()); + imageView.setImageResource(R.drawable.ic_close_white_24dp); + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + disposable = new CompositeDisposable(); + } + + @OnClick({R.id.next, R.id.banner_select}) + void next(View view) { + switch (view.getId()) { + case R.id.banner_select: + showBannerOptions(); + break; + case R.id.next: + String nameString = name.getText().toString().trim(); + if (TextUtils.isEmpty(nameString)) { + Toast.makeText(getActivity(), "Umm name is empty", Toast.LENGTH_SHORT).show(); + return; + } + //noinspection ConstantConditions + PreferenceUtil.getInstance(getActivity()).setUserName(nameString); + getActivity().setResult(RESULT_OK); + //((UserInfoActivity) getActivity()).setFragment(new ChooseThemeFragment(), true); + getActivity().finish(); + break; + } + } + + private void showBannerOptions() { + //noinspection ConstantConditions + new Builder(getContext()) + .title(R.string.select_banner_photo) + .items(Arrays.asList(getString(R.string.new_banner_photo), + getString(R.string.remove_banner_photo))) + .itemsCallback((dialog, itemView, position, text) -> { + switch (position) { + case 0: + selectBannerImage(); + break; + case 1: + PreferenceUtil.getInstance(getContext()).setBannerImagePath(""); + break; + } + }).show(); + } + + private void selectBannerImage() { + //noinspection ConstantConditions + if (PreferenceUtil.getInstance(getActivity()).getBannerImage().isEmpty()) { + Intent pickImageIntent = new Intent(Intent.ACTION_PICK, + Media.EXTERNAL_CONTENT_URI); + pickImageIntent.setType("image/*"); + pickImageIntent.putExtra("crop", "true"); + pickImageIntent.putExtra("outputX", 1290); + pickImageIntent.putExtra("outputY", 720); + pickImageIntent.putExtra("aspectX", 16); + pickImageIntent.putExtra("aspectY", 9); + pickImageIntent.putExtra("scale", true); + //intent.setAction(Intent.ACTION_GET_CONTENT); + startActivityForResult(Intent.createChooser(pickImageIntent, + "Select Picture"), PICK_BANNER_REQUEST); + } else { + PreferenceUtil.getInstance(getContext()).setBannerImagePath(""); + image.setImageResource(android.R.color.transparent); + imageView.setImageResource(R.drawable.ic_edit_white_24dp); + } + } + + @OnClick(R.id.image) + public void onViewClicked() { + //noinspection ConstantConditions + new Builder(getContext()) + .title("Set a profile photo") + .items(Arrays.asList(getString(R.string.new_profile_photo), + getString(R.string.remove_profile_photo))) + .itemsCallback((dialog, itemView, position, text) -> { + switch (position) { + case 0: + pickNewPhoto(); + break; + case 1: + PreferenceUtil.getInstance(getContext()).saveProfileImage(""); + break; + } + }).show(); + } + + private void pickNewPhoto() { + Intent pickImageIntent = new Intent(Intent.ACTION_PICK, + android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + pickImageIntent.setType("image/*"); + pickImageIntent.putExtra("crop", "true"); + pickImageIntent.putExtra("outputX", 512); + pickImageIntent.putExtra("outputY", 512); + pickImageIntent.putExtra("aspectX", 1); + pickImageIntent.putExtra("aspectY", 1); + pickImageIntent.putExtra("scale", true); + startActivityForResult(Intent.createChooser(pickImageIntent, "Select Picture"), + PICK_IMAGE_REQUEST); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && + data.getData() != null) { + Uri uri = data.getData(); + try { + Bitmap bitmap = ImageUtil.getResizedBitmap(Media.getBitmap(getActivity() + .getContentResolver(), uri), PROFILE_ICON_SIZE); + String profileImagePath = saveToInternalStorage(bitmap, USER_PROFILE); + PreferenceUtil.getInstance(getActivity()).saveProfileImage(profileImagePath); + loadImageFromStorage(profileImagePath); + + } catch (IOException e) { + e.printStackTrace(); + } + } + if (requestCode == PICK_BANNER_REQUEST && resultCode == RESULT_OK && data != null && + data.getData() != null) { + Uri uri = data.getData(); + try { + Bitmap bitmap = Media.getBitmap(getActivity().getContentResolver(), uri); + String profileImagePath = saveToInternalStorage(bitmap, USER_BANNER); + PreferenceUtil.getInstance(getActivity()).setBannerImagePath(profileImagePath); + loadBannerFromStorage(profileImagePath); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void loadBannerFromStorage(String profileImagePath) { + disposable.add(new Compressor(getActivity()) + .setQuality(100) + .setCompressFormat(Bitmap.CompressFormat.WEBP) + .compressToBitmapAsFlowable(new File(profileImagePath, USER_BANNER)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(bitmap -> { + image.setImageBitmap(bitmap); + })); + } + + private void loadImageFromStorage(String path) { + disposable.add(new Compressor(getActivity()) + .setMaxHeight(300) + .setMaxWidth(300) + .setQuality(75) + .setCompressFormat(Bitmap.CompressFormat.WEBP) + .compressToBitmapAsFlowable(new File(path, USER_PROFILE)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(bitmap -> { + userImage.setImageBitmap(bitmap); + })); + } + + private String saveToInternalStorage(Bitmap bitmapImage, String userBanner) { + ContextWrapper cw = new ContextWrapper(getActivity()); + // path to /data/data/yourapp/app_data/imageDir + File directory = cw.getDir("imageDir", Context.MODE_PRIVATE); + // Create imageDir + File mypath = new File(directory, userBanner); + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(mypath); + // Use the compress method on the BitMap object to write image to the OutputStream + bitmapImage.compress(Bitmap.CompressFormat.WEBP, 100, fos); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return directory.getAbsolutePath(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/AlbumsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/AlbumsFragment.java new file mode 100644 index 00000000..ccdf51f4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/AlbumsFragment.java @@ -0,0 +1,168 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.GridLayoutManager; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.mvp.contract.AlbumContract; +import code.name.monkey.retromusic.mvp.presenter.AlbumPresenter; +import code.name.monkey.retromusic.ui.adapter.album.AlbumAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsLibraryPagerRecyclerViewCustomGridSizeFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; + +public class AlbumsFragment extends + AbsLibraryPagerRecyclerViewCustomGridSizeFragment implements + AlbumContract.AlbumView { + + public static final String TAG = AlbumsFragment.class.getSimpleName(); + + private AlbumPresenter presenter; + + public static AlbumsFragment newInstance() { + Bundle args = new Bundle(); + AlbumsFragment fragment = new AlbumsFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + protected GridLayoutManager createLayoutManager() { + return new GridLayoutManager(getActivity(), getGridSize()); + } + + @NonNull + @Override + protected AlbumAdapter createAdapter() { + int itemLayoutRes = getItemLayoutRes(); + notifyLayoutResChanged(itemLayoutRes); + if (itemLayoutRes != R.layout.item_list) { + itemLayoutRes = PreferenceUtil.getInstance(getContext()).getAlbumGridStyle(getContext()); + } + ArrayList dataSet = + getAdapter() == null ? new ArrayList<>() : getAdapter().getDataSet(); + return new AlbumAdapter(getLibraryFragment().getMainActivity(), dataSet, itemLayoutRes, + loadUsePalette(), getLibraryFragment()); + } + + @Override + protected int getEmptyMessage() { + return R.string.no_albums; + } + + @Override + public boolean loadUsePalette() { + return PreferenceUtil.getInstance(getActivity()).albumColoredFooters(); + } + + @Override + protected void setUsePalette(boolean usePalette) { + getAdapter().usePalette(usePalette); + } + + @Override + protected void setGridSize(int gridSize) { + getLayoutManager().setSpanCount(gridSize); + getAdapter().notifyDataSetChanged(); + } + + @Override + protected void setSortOrder(String sortOrder) { + presenter.loadAlbums(); + } + + @Override + protected String loadSortOrder() { + return PreferenceUtil.getInstance(getActivity()).getAlbumSortOrder(); + } + + @Override + protected void saveSortOrder(String sortOrder) { + PreferenceUtil.getInstance(getActivity()).setAlbumSortOrder(sortOrder); + } + + @Override + protected int loadGridSize() { + return PreferenceUtil.getInstance(getActivity()).getAlbumGridSize(getActivity()); + } + + @Override + protected void saveGridSize(int gridSize) { + PreferenceUtil.getInstance(getActivity()).setAlbumGridSize(gridSize); + } + + @Override + protected int loadGridSizeLand() { + return PreferenceUtil.getInstance(getActivity()).getAlbumGridSizeLand(getActivity()); + } + + @Override + protected void saveGridSizeLand(int gridSize) { + PreferenceUtil.getInstance(getActivity()).setAlbumGridSizeLand(gridSize); + } + + @Override + protected void saveUsePalette(boolean usePalette) { + PreferenceUtil.getInstance(getActivity()).setAlbumColoredFooters(usePalette); + } + + @Override + public void onMediaStoreChanged() { + presenter.loadAlbums(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + presenter = new AlbumPresenter(this); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (menuVisible) { + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library + : R.string.albums); + } + } + + @Override + public void onResume() { + super.onResume(); + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library : R.string.albums); + if (getAdapter().getDataSet().isEmpty()) { + presenter.subscribe(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + presenter.unsubscribe(); + } + + @Override + public void loading() { + } + + @Override + public void showEmptyView() { + getAdapter().swapDataSet(new ArrayList<>()); + } + + @Override + public void completed() { + } + + @Override + public void showData(ArrayList albums) { + getAdapter().swapDataSet(albums); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/ArtistsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/ArtistsFragment.java new file mode 100644 index 00000000..82069802 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/ArtistsFragment.java @@ -0,0 +1,173 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.GridLayoutManager; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.mvp.contract.ArtistContract; +import code.name.monkey.retromusic.mvp.presenter.ArtistPresenter; +import code.name.monkey.retromusic.ui.adapter.artist.ArtistAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsLibraryPagerRecyclerViewCustomGridSizeFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; + +public class ArtistsFragment extends + AbsLibraryPagerRecyclerViewCustomGridSizeFragment implements + ArtistContract.ArtistView { + + public static final String TAG = ArtistsFragment.class.getSimpleName(); + private ArtistPresenter presenter; + + public static ArtistsFragment newInstance() { + + Bundle args = new Bundle(); + + ArtistsFragment fragment = new ArtistsFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + presenter = new ArtistPresenter(this); + } + + @NonNull + @Override + protected GridLayoutManager createLayoutManager() { + return new GridLayoutManager(getActivity(), getGridSize()); + } + + @NonNull + @Override + protected ArtistAdapter createAdapter() { + int itemLayoutRes = getItemLayoutRes(); + notifyLayoutResChanged(itemLayoutRes); + if (itemLayoutRes != R.layout.item_list) { + itemLayoutRes = PreferenceUtil.getInstance(getContext()).getArtistGridStyle(getContext()); + } + ArrayList dataSet = + getAdapter() == null ? new ArrayList<>() : getAdapter().getDataSet(); + return new ArtistAdapter(getLibraryFragment().getMainActivity(), dataSet, itemLayoutRes, + loadUsePalette(), getLibraryFragment()); + } + + @Override + protected int getEmptyMessage() { + return R.string.no_artists; + } + + @Override + public void onMediaStoreChanged() { + presenter.loadArtists(); + } + + @Override + protected int loadGridSize() { + return PreferenceUtil.getInstance(getActivity()).getArtistGridSize(getActivity()); + } + + @Override + protected void saveGridSize(int gridSize) { + PreferenceUtil.getInstance(getActivity()).setArtistGridSize(gridSize); + } + + @Override + protected int loadGridSizeLand() { + return PreferenceUtil.getInstance(getActivity()).getArtistGridSizeLand(getActivity()); + } + + @Override + protected void saveGridSizeLand(int gridSize) { + PreferenceUtil.getInstance(getActivity()).setArtistGridSizeLand(gridSize); + } + + @Override + protected void saveUsePalette(boolean usePalette) { + PreferenceUtil.getInstance(getActivity()).setArtistColoredFooters(usePalette); + } + + @Override + public boolean loadUsePalette() { + return PreferenceUtil.getInstance(getActivity()).artistColoredFooters(); + } + + @Override + protected void setUsePalette(boolean usePalette) { + getAdapter().usePalette(usePalette); + } + + @Override + protected void setGridSize(int gridSize) { + getLayoutManager().setSpanCount(gridSize); + getAdapter().notifyDataSetChanged(); + } + + + @Override + protected String loadSortOrder() { + return PreferenceUtil.getInstance(getActivity()).getArtistSortOrder(); + } + + @Override + protected void saveSortOrder(String sortOrder) { + PreferenceUtil.getInstance(getActivity()).setArtistSortOrder(sortOrder); + } + + @Override + protected void setSortOrder(String sortOrder) { + presenter.loadArtists(); + } + + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (menuVisible) { + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library + : R.string.artists); + } + } + + @Override + public void onResume() { + super.onResume(); + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library : R.string.artists); + if (getAdapter().getDataSet().isEmpty()) { + presenter.subscribe(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + presenter.unsubscribe(); + } + + @Override + public void loading() { + } + + @Override + public void showEmptyView() { + getAdapter().swapDataSet(new ArrayList<>()); + } + + @Override + public void completed() { + + } + + @Override + public void showData(ArrayList artists) { + getAdapter().swapDataSet(artists); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/GenreFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/GenreFragment.java new file mode 100644 index 00000000..9925b60f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/GenreFragment.java @@ -0,0 +1,112 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.view.Menu; +import android.view.MenuInflater; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.mvp.contract.GenreContract; +import code.name.monkey.retromusic.mvp.presenter.GenrePresenter; +import code.name.monkey.retromusic.ui.adapter.GenreAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsLibraryPagerRecyclerViewFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; +import java.util.ArrayList; + +public class GenreFragment extends + AbsLibraryPagerRecyclerViewFragment implements + GenreContract.GenreView { + + private GenrePresenter mPresenter; + + public static GenreFragment newInstance() { + Bundle args = new Bundle(); + GenreFragment fragment = new GenreFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + mPresenter = new GenrePresenter(this); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (menuVisible) { + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library + : R.string.genres); + } + } + + @Override + public void onResume() { + super.onResume(); + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library : R.string.genres); + if (getAdapter().getDataSet().isEmpty()) { + mPresenter.subscribe(); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + mPresenter.unsubscribe(); + } + + @NonNull + @Override + protected LinearLayoutManager createLayoutManager() { + return new LinearLayoutManager(getActivity()); + } + + @NonNull + @Override + protected GenreAdapter createAdapter() { + ArrayList dataSet = + getAdapter() == null ? new ArrayList() : getAdapter().getDataSet(); + return new GenreAdapter(getLibraryFragment().getMainActivity(), dataSet, R.layout.item_list); + } + + @Override + public void loading() { + + } + + @Override + public void showData(ArrayList songs) { + getAdapter().swapDataSet(songs); + } + + @Override + public void showEmptyView() { + getAdapter().swapDataSet(new ArrayList()); + } + + @Override + public void completed() { + + } + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.removeItem(R.id.action_sort_order); + menu.removeItem(R.id.action_grid_size); + menu.removeItem(R.id.action_new_playlist); + } + + @Override + protected int getEmptyMessage() { + return R.string.no_genres; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/LibraryFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/LibraryFragment.java new file mode 100644 index 00000000..77e94be9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/LibraryFragment.java @@ -0,0 +1,458 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.ViewGroup; + +import com.afollestad.materialcab.MaterialCab; + +import java.util.Objects; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.common.ATHToolbarActivity; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog; +import code.name.monkey.retromusic.dialogs.SleepTimerDialog; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.SortOrder; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.ui.activities.SearchActivity; +import code.name.monkey.retromusic.ui.activities.SettingsActivity; +import code.name.monkey.retromusic.ui.fragments.base.AbsLibraryPagerRecyclerViewCustomGridSizeFragment; +import code.name.monkey.retromusic.ui.fragments.base.AbsMainActivityFragment; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.RetroColorUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.views.SansFontCollapsingToolbarLayout; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class LibraryFragment extends AbsMainActivityFragment implements CabHolder, + MainActivityFragmentCallbacks { + + private static final String TAG = "LibraryFragment"; + private static final String CURRENT_TAB_ID = "current_tab_id"; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.appbar) + AppBarLayout appbar; + @BindView(R.id.collapsing_toolbar) + SansFontCollapsingToolbarLayout collapsingToolbar; + + private Unbinder unBinder; + private MaterialCab cab; + private FragmentManager fragmentManager; + + public static Fragment newInstance(int tab) { + Bundle args = new Bundle(); + args.putInt(CURRENT_TAB_ID, tab); + LibraryFragment fragment = new LibraryFragment(); + fragment.setArguments(args); + return fragment; + } + + public SansFontCollapsingToolbarLayout getToolbar() { + return collapsingToolbar; + } + + public void addOnAppBarOffsetChangedListener( + AppBarLayout.OnOffsetChangedListener onOffsetChangedListener) { + appbar.addOnOffsetChangedListener(onOffsetChangedListener); + } + + public void removeOnAppBarOffsetChangedListener( + AppBarLayout.OnOffsetChangedListener onOffsetChangedListener) { + appbar.removeOnOffsetChangedListener(onOffsetChangedListener); + } + + public int getTotalAppBarScrollingRange() { + return appbar.getTotalScrollRange(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_library, container, false); + unBinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setStatusbarColorAuto(view); + + getMainActivity().setBottomBarVisibility(View.VISIBLE); + setupToolbar(); + if (getArguments() == null) { + selectedFragment(SongsFragment.newInstance()); + return; + } + switch (getArguments().getInt(CURRENT_TAB_ID)) { + default: + case R.id.action_song: + selectedFragment(SongsFragment.newInstance()); + break; + case R.id.action_album: + selectedFragment(AlbumsFragment.newInstance()); + break; + case R.id.action_artist: + selectedFragment(ArtistsFragment.newInstance()); + break; + case R.id.action_playlist: + selectedFragment(PlaylistsFragment.newInstance()); + break; + } + } + + private void setupToolbar() { + //noinspection ConstantConditions + int primaryColor = ThemeStore.primaryColor(getContext()); + appbar.setBackgroundColor(primaryColor); + toolbar.setBackgroundColor(primaryColor); + toolbar.setTitle(R.string.library); + appbar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> + getMainActivity().setLightStatusbar(!ATHUtil.isWindowBackgroundDark(getContext()))); + Objects.requireNonNull(getActivity()).setTitle(R.string.app_name); + getMainActivity().setSupportActionBar(toolbar); + } + + public Fragment getCurrentFragment() { + if (fragmentManager == null) { + return SongsFragment.newInstance(); + } + return fragmentManager.findFragmentByTag(LibraryFragment.TAG); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unBinder.unbind(); + } + + @Override + public boolean handleBackPress() { + if (cab != null && cab.isActive()) { + cab.finish(); + return true; + } + return false; + } + + public void selectedFragment(Fragment fragment) { + fragmentManager = getChildFragmentManager(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); + + fragmentTransaction + .replace(R.id.fragment_container, fragment, TAG) + .commit(); + } + + @NonNull + @Override + public MaterialCab openCab(int menuRes, MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) { + cab.finish(); + } + //noinspection ConstantConditions + cab = new MaterialCab(getMainActivity(), R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor( + RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(getActivity()))) + .start(callback); + return cab; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.menu_main, menu); + + Fragment currentFragment = getCurrentFragment(); + if (currentFragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment + && currentFragment.isAdded()) { + AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment = (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) currentFragment; + + MenuItem gridSizeItem = menu.findItem(R.id.action_grid_size); + if (RetroUtil.isLandscape(getResources())) { + gridSizeItem.setTitle(R.string.action_grid_size_land); + } + setUpGridSizeMenu(fragment, gridSizeItem.getSubMenu()); + + setUpSortOrderMenu(fragment, menu.findItem(R.id.action_sort_order).getSubMenu()); + + } else { + menu.add(0, R.id.action_new_playlist, 0, R.string.new_playlist_title); + menu.removeItem(R.id.action_grid_size); + } + Activity activity = getActivity(); + if (activity == null) { + return; + } + ToolbarContentTintHelper.handleOnCreateOptionsMenu(getActivity(), toolbar, menu, + ATHToolbarActivity.getToolbarBackgroundColor(toolbar)); + } + + private void setUpSortOrderMenu( + @NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, + @NonNull SubMenu sortOrderMenu) { + String currentSortOrder = fragment.getSortOrder(); + sortOrderMenu.clear(); + + if (fragment instanceof AlbumsFragment) { + sortOrderMenu.add(0, R.id.action_album_sort_order_asc, 0, R.string.sort_order_a_z) + .setChecked(currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z)); + sortOrderMenu.add(0, R.id.action_album_sort_order_desc, 1, R.string.sort_order_z_a) + .setChecked(currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A)); + sortOrderMenu.add(0, R.id.action_album_sort_order_artist, 2, R.string.sort_order_artist) + .setChecked(currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST)); + sortOrderMenu.add(0, R.id.action_album_sort_order_year, 3, R.string.sort_order_year) + .setChecked(currentSortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_YEAR)); + } else if (fragment instanceof ArtistsFragment) { + sortOrderMenu.add(0, R.id.action_artist_sort_order_asc, 0, R.string.sort_order_a_z) + .setChecked(currentSortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z)); + sortOrderMenu.add(0, R.id.action_artist_sort_order_desc, 1, R.string.sort_order_z_a) + .setChecked(currentSortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A)); + } else if (fragment instanceof SongsFragment) { + sortOrderMenu.add(0, R.id.action_song_sort_order_asc, 0, R.string.sort_order_a_z) + .setChecked(currentSortOrder.equals(SortOrder.SongSortOrder.SONG_A_Z)); + sortOrderMenu.add(0, R.id.action_song_sort_order_desc, 1, R.string.sort_order_z_a) + .setChecked(currentSortOrder.equals(SortOrder.SongSortOrder.SONG_Z_A)); + sortOrderMenu.add(0, R.id.action_song_sort_order_artist, 2, R.string.sort_order_artist) + .setChecked(currentSortOrder.equals(SortOrder.SongSortOrder.SONG_ARTIST)); + sortOrderMenu.add(0, R.id.action_song_sort_order_album, 3, R.string.sort_order_album) + .setChecked(currentSortOrder.equals(SortOrder.SongSortOrder.SONG_ALBUM)); + sortOrderMenu.add(0, R.id.action_song_sort_order_year, 4, R.string.sort_order_year) + .setChecked(currentSortOrder.equals(SortOrder.SongSortOrder.SONG_YEAR)); + sortOrderMenu.add(0, R.id.action_song_sort_order_date, 4, R.string.sort_order_date) + .setChecked(currentSortOrder.equals(SortOrder.SongSortOrder.SONG_DATE)); + } + + sortOrderMenu.setGroupCheckable(0, true, true); + } + + private boolean handleSortOrderMenuItem( + @NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull MenuItem item) { + String sortOrder = null; + if (fragment instanceof AlbumsFragment) { + switch (item.getItemId()) { + case R.id.action_album_sort_order_asc: + sortOrder = SortOrder.AlbumSortOrder.ALBUM_A_Z; + break; + case R.id.action_album_sort_order_desc: + sortOrder = SortOrder.AlbumSortOrder.ALBUM_Z_A; + break; + case R.id.action_album_sort_order_artist: + sortOrder = SortOrder.AlbumSortOrder.ALBUM_ARTIST; + break; + case R.id.action_album_sort_order_year: + sortOrder = SortOrder.AlbumSortOrder.ALBUM_YEAR; + break; + } + } else if (fragment instanceof ArtistsFragment) { + switch (item.getItemId()) { + case R.id.action_artist_sort_order_asc: + sortOrder = SortOrder.ArtistSortOrder.ARTIST_A_Z; + break; + case R.id.action_artist_sort_order_desc: + sortOrder = SortOrder.ArtistSortOrder.ARTIST_Z_A; + break; + } + } else if (fragment instanceof SongsFragment) { + switch (item.getItemId()) { + case R.id.action_song_sort_order_asc: + sortOrder = SortOrder.SongSortOrder.SONG_A_Z; + break; + case R.id.action_song_sort_order_desc: + sortOrder = SortOrder.SongSortOrder.SONG_Z_A; + break; + case R.id.action_song_sort_order_artist: + sortOrder = SortOrder.SongSortOrder.SONG_ARTIST; + break; + case R.id.action_song_sort_order_album: + sortOrder = SortOrder.SongSortOrder.SONG_ALBUM; + break; + case R.id.action_song_sort_order_year: + sortOrder = SortOrder.SongSortOrder.SONG_YEAR; + break; + case R.id.action_song_sort_order_date: + sortOrder = SortOrder.SongSortOrder.SONG_DATE; + break; + } + } + + if (sortOrder != null) { + item.setChecked(true); + fragment.setAndSaveSortOrder(sortOrder); + return true; + } + + return false; + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + Activity activity = getActivity(); + if (activity == null) { + return; + } + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(activity, toolbar); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + //if (pager == null) return false; + Fragment currentFragment = getCurrentFragment(); + if (currentFragment instanceof AbsLibraryPagerRecyclerViewCustomGridSizeFragment) { + AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment = (AbsLibraryPagerRecyclerViewCustomGridSizeFragment) currentFragment; + if (handleGridSizeMenuItem(fragment, item)) { + return true; + } + if (handleSortOrderMenuItem(fragment, item)) { + return true; + } + } + int id = item.getItemId(); + switch (id) { + case R.id.action_new_playlist: + CreatePlaylistDialog.create().show(getChildFragmentManager(), "CREATE_PLAYLIST"); + return true; + case R.id.action_shuffle_all: + //noinspection ConstantConditions + SongLoader.getAllSongs(getContext()).subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(songs -> MusicPlayerRemote.openAndShuffleQueue(songs, true)); + return true; + case R.id.action_search: + startActivity(new Intent(getActivity(), SearchActivity.class)); + return true; + case R.id.action_equalizer: + //noinspection ConstantConditions + NavigationUtil.openEqualizer(getActivity()); + return true; + case R.id.action_sleep_timer: + if (getFragmentManager() != null) { + new SleepTimerDialog().show(getFragmentManager(), TAG); + } + return true; + case R.id.action_settings: + startActivity(new Intent(getContext(), SettingsActivity.class)); + break; + } + return super.onOptionsItemSelected(item); + } + + private void setUpGridSizeMenu( + @NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, + @NonNull SubMenu gridSizeMenu) { + switch (fragment.getGridSize()) { + case 1: + gridSizeMenu.findItem(R.id.action_grid_size_1).setChecked(true); + break; + case 2: + gridSizeMenu.findItem(R.id.action_grid_size_2).setChecked(true); + break; + case 3: + gridSizeMenu.findItem(R.id.action_grid_size_3).setChecked(true); + break; + case 4: + gridSizeMenu.findItem(R.id.action_grid_size_4).setChecked(true); + break; + case 5: + gridSizeMenu.findItem(R.id.action_grid_size_5).setChecked(true); + break; + case 6: + gridSizeMenu.findItem(R.id.action_grid_size_6).setChecked(true); + break; + case 7: + gridSizeMenu.findItem(R.id.action_grid_size_7).setChecked(true); + break; + case 8: + gridSizeMenu.findItem(R.id.action_grid_size_8).setChecked(true); + break; + } + int maxGridSize = fragment.getMaxGridSize(); + if (maxGridSize < 8) { + gridSizeMenu.findItem(R.id.action_grid_size_8).setVisible(false); + } + if (maxGridSize < 7) { + gridSizeMenu.findItem(R.id.action_grid_size_7).setVisible(false); + } + if (maxGridSize < 6) { + gridSizeMenu.findItem(R.id.action_grid_size_6).setVisible(false); + } + if (maxGridSize < 5) { + gridSizeMenu.findItem(R.id.action_grid_size_5).setVisible(false); + } + if (maxGridSize < 4) { + gridSizeMenu.findItem(R.id.action_grid_size_4).setVisible(false); + } + if (maxGridSize < 3) { + gridSizeMenu.findItem(R.id.action_grid_size_3).setVisible(false); + } + } + + + private boolean handleGridSizeMenuItem( + @NonNull AbsLibraryPagerRecyclerViewCustomGridSizeFragment fragment, @NonNull MenuItem item) { + int gridSize = 0; + switch (item.getItemId()) { + case R.id.action_grid_size_1: + gridSize = 1; + break; + case R.id.action_grid_size_2: + gridSize = 2; + break; + case R.id.action_grid_size_3: + gridSize = 3; + break; + case R.id.action_grid_size_4: + gridSize = 4; + break; + case R.id.action_grid_size_5: + gridSize = 5; + break; + case R.id.action_grid_size_6: + gridSize = 6; + break; + case R.id.action_grid_size_7: + gridSize = 7; + break; + case R.id.action_grid_size_8: + gridSize = 8; + break; + } + + if (gridSize > 0) { + item.setChecked(true); + fragment.setAndSaveGridSize(gridSize); + return true; + } + return false; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/PlaylistsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/PlaylistsFragment.java new file mode 100644 index 00000000..2850ed94 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/PlaylistsFragment.java @@ -0,0 +1,120 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; + +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.mvp.contract.PlaylistContract; +import code.name.monkey.retromusic.mvp.presenter.PlaylistPresenter; +import code.name.monkey.retromusic.ui.adapter.playlist.PlaylistAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsLibraryPagerRecyclerViewFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; + + +public class PlaylistsFragment extends AbsLibraryPagerRecyclerViewFragment implements + PlaylistContract.PlaylistView { + + private PlaylistPresenter presenter; + + public static PlaylistsFragment newInstance() { + Bundle args = new Bundle(); + PlaylistsFragment fragment = new PlaylistsFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + presenter = new PlaylistPresenter(this); + } + + @Override + protected LinearLayoutManager createLayoutManager() { + return new LinearLayoutManager(getActivity()); + } + + @NonNull + @Override + protected PlaylistAdapter createAdapter() { + return new PlaylistAdapter(getLibraryFragment().getMainActivity(), new ArrayList<>(), + R.layout.item_list, getLibraryFragment()); + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (menuVisible) { + getLibraryFragment().getToolbar().setTitle(PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library : R.string.playlists); + } + } + + @Override + public void onResume() { + super.onResume(); + getLibraryFragment().getToolbar().setTitle(PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library : R.string.playlists); + if (getAdapter().getDataSet().isEmpty()) { + presenter.subscribe(); + } + } + + @Override + public void onDestroy() { + presenter.unsubscribe(); + super.onDestroy(); + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + presenter.loadPlaylists(); + } + + @Override + public void loading() { + + } + + @Override + public void showEmptyView() { + getAdapter().swapDataSet(new ArrayList<>()); + } + + @Override + public void completed() { + + } + + @Override + public void showData(ArrayList playlists) { + getAdapter().swapDataSet(playlists); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.removeItem(R.id.action_shuffle_all); + menu.removeItem(R.id.action_sort_order); + menu.removeItem(R.id.action_grid_size); + } + + @Override + protected int getEmptyMessage() { + return R.string.no_playlists; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/SongsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/SongsFragment.java new file mode 100644 index 00000000..c2fac873 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/SongsFragment.java @@ -0,0 +1,178 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.GridLayoutManager; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.mvp.contract.SongContract; +import code.name.monkey.retromusic.mvp.presenter.SongPresenter; +import code.name.monkey.retromusic.ui.adapter.song.ShuffleButtonSongAdapter; +import code.name.monkey.retromusic.ui.adapter.song.SongAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsLibraryPagerRecyclerViewCustomGridSizeFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; + +import java.util.ArrayList; + +@SuppressWarnings("ConstantConditions") +public class SongsFragment extends + AbsLibraryPagerRecyclerViewCustomGridSizeFragment implements + SongContract.SongView { + + private static final String TAG = "Songs"; + private SongPresenter presenter; + + public SongsFragment() { + // Required empty public constructor + } + + public static SongsFragment newInstance() { + Bundle args = new Bundle(); + SongsFragment fragment = new SongsFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + presenter = new SongPresenter(this); + } + + @NonNull + @Override + protected GridLayoutManager createLayoutManager() { + return new GridLayoutManager(getActivity(), getGridSize()); + } + + @Override + protected int getEmptyMessage() { + return R.string.no_songs; + } + + @NonNull + @Override + protected SongAdapter createAdapter() { + int itemLayoutRes = getItemLayoutRes(); + notifyLayoutResChanged(itemLayoutRes); + boolean usePalette = loadUsePalette(); + ArrayList dataSet = + getAdapter() == null ? new ArrayList() : getAdapter().getDataSet(); + + if (getGridSize() <= getMaxGridSizeForList()) { + return new ShuffleButtonSongAdapter(getLibraryFragment().getMainActivity(), dataSet, + itemLayoutRes, usePalette, getLibraryFragment()); + } + return new SongAdapter(getLibraryFragment().getMainActivity(), dataSet, itemLayoutRes, + usePalette, getLibraryFragment()); + } + + @Override + public void onMediaStoreChanged() { + presenter.loadSongs(); + } + + @Override + protected int loadGridSize() { + return PreferenceUtil.getInstance(getActivity()).getSongGridSize(getActivity()); + } + + @Override + protected void saveGridSize(int gridSize) { + PreferenceUtil.getInstance(getActivity()).setSongGridSize(gridSize); + } + + @Override + protected int loadGridSizeLand() { + return PreferenceUtil.getInstance(getActivity()).getSongGridSizeLand(getActivity()); + } + + @Override + protected void saveGridSizeLand(int gridSize) { + PreferenceUtil.getInstance(getActivity()).setSongGridSizeLand(gridSize); + } + + @Override + public void saveUsePalette(boolean usePalette) { + PreferenceUtil.getInstance(getActivity()).setSongColoredFooters(usePalette); + } + + @Override + public boolean loadUsePalette() { + return PreferenceUtil.getInstance(getActivity()).songColoredFooters(); + } + + @Override + public void setUsePalette(boolean usePalette) { + getAdapter().usePalette(usePalette); + } + + @Override + protected void setGridSize(int gridSize) { + getLayoutManager().setSpanCount(gridSize); + getAdapter().notifyDataSetChanged(); + } + + @Override + public void onResume() { + super.onResume(); + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library : R.string.songs); + if (getAdapter().getDataSet().isEmpty()) { + presenter.subscribe(); + } + } + + @Override + public void setMenuVisibility(boolean menuVisible) { + super.setMenuVisibility(menuVisible); + if (menuVisible) { + getLibraryFragment().getToolbar().setTitle( + PreferenceUtil.getInstance(getContext()).tabTitles() ? R.string.library + : R.string.songs); + } + } + + @Override + public void onDestroy() { + presenter.unsubscribe(); + super.onDestroy(); + } + + @Override + public void loading() { + + } + + @Override + public void showData(ArrayList songs) { + getAdapter().swapDataSet(songs); + } + + @Override + public void showEmptyView() { + getAdapter().swapDataSet(new ArrayList()); + } + + @Override + public void completed() { + + } + + @Override + protected String loadSortOrder() { + return PreferenceUtil.getInstance(getActivity()).getSongSortOrder(); + } + + @Override + protected void saveSortOrder(String sortOrder) { + PreferenceUtil.getInstance(getActivity()).setSongSortOrder(sortOrder); + } + + @Override + protected void setSortOrder(String sortOrder) { + presenter.loadSongs(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/folders/FoldersFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/folders/FoldersFragment.java new file mode 100644 index 00000000..b7a687a9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/folders/FoldersFragment.java @@ -0,0 +1,697 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity.folders; + +import android.app.Dialog; +import android.content.Context; +import android.media.MediaScannerConnection; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.text.Html; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.MimeTypeMap; +import android.widget.PopupMenu; +import android.widget.Toast; + +import com.afollestad.materialcab.MaterialCab; +import com.afollestad.materialdialogs.MaterialDialog; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.common.ATHToolbarActivity; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.menu.SongMenuHelper; +import code.name.monkey.retromusic.helper.menu.SongsMenuHelper; +import code.name.monkey.retromusic.interfaces.CabHolder; +import code.name.monkey.retromusic.interfaces.LoaderIds; +import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks; +import code.name.monkey.retromusic.misc.DialogAsyncTask; +import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; +import code.name.monkey.retromusic.misc.WrappedAsyncTaskLoader; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.adapter.SongFileAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsMainActivityFragment; +import code.name.monkey.retromusic.util.FileUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroColorUtil; +import code.name.monkey.retromusic.util.ViewUtil; +import code.name.monkey.retromusic.views.BreadCrumbLayout; + +public class FoldersFragment extends AbsMainActivityFragment implements + MainActivityFragmentCallbacks, + CabHolder, BreadCrumbLayout.SelectionCallback, SongFileAdapter.Callbacks, + AppBarLayout.OnOffsetChangedListener, LoaderManager.LoaderCallbacks> { + + public static final String TAG = FoldersFragment.class.getSimpleName(); + public static final FileFilter AUDIO_FILE_FILTER = file -> !file.isHidden() && (file.isDirectory() || + FileUtil.fileIsMimeType(file, "audio/*", MimeTypeMap.getSingleton()) || + FileUtil.fileIsMimeType(file, "application/opus", MimeTypeMap.getSingleton()) || + FileUtil.fileIsMimeType(file, "application/ogg", MimeTypeMap.getSingleton())); + + protected static final String PATH = "path"; + protected static final String CRUMBS = "crumbs"; + private static final int LOADER_ID = LoaderIds.FOLDERS_FRAGMENT; + @BindView(R.id.coordinator_layout) + CoordinatorLayout coordinatorLayout; + @BindView(R.id.container) + View container; + @BindView(android.R.id.empty) + View empty; + @BindView(R.id.toolbar) + Toolbar toolbar; + @BindView(R.id.bread_crumbs) + BreadCrumbLayout breadCrumbs; + @BindView(R.id.appbar) + AppBarLayout appbar; + @BindView(R.id.status_bar) + View statusBar; + @BindView(R.id.recycler_view) + FastScrollRecyclerView recyclerView; + Comparator fileComparator = (lhs, rhs) -> { + if (lhs.isDirectory() && !rhs.isDirectory()) { + return -1; + } else if (!lhs.isDirectory() && rhs.isDirectory()) { + return 1; + } else { + return lhs.getName().compareToIgnoreCase + (rhs.getName()); + } + }; + + private Unbinder unbinder; + private SongFileAdapter adapter; + private MaterialCab cab; + + public FoldersFragment() { + } + + public static FoldersFragment newInstance(Context context) { + return newInstance(PreferenceUtil.getInstance(context).getStartDirectory()); + } + + public static FoldersFragment newInstance(File directory) { + FoldersFragment frag = new FoldersFragment(); + Bundle b = new Bundle(); + b.putSerializable(PATH, directory); + frag.setArguments(b); + return frag; + } + + + public static File getDefaultStartDirectory() { + File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); + File startFolder; + if (musicDir.exists() && musicDir.isDirectory()) { + startFolder = musicDir; + } else { + File externalStorage = Environment.getExternalStorageDirectory(); + if (externalStorage.exists() && externalStorage.isDirectory()) { + startFolder = externalStorage; + } else { + startFolder = new File("/"); // root + } + } + return startFolder; + } + + private static File tryGetCanonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + e.printStackTrace(); + return file; + } + } + + public void setCrumb(BreadCrumbLayout.Crumb crumb, boolean addToHistory) { + if (crumb == null) { + return; + } + saveScrollPosition(); + breadCrumbs.setActiveOrAdd(crumb, false); + if (addToHistory) { + breadCrumbs.addHistory(crumb); + } + getLoaderManager().restartLoader(LOADER_ID, null, this); + } + + private void saveScrollPosition() { + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null) { + crumb.setScrollPosition( + ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition()); + } + } + + @Nullable + private BreadCrumbLayout.Crumb getActiveCrumb() { + return breadCrumbs != null && breadCrumbs.size() > 0 ? breadCrumbs + .getCrumb(breadCrumbs.getActiveIndex()) : null; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(CRUMBS, breadCrumbs.getStateWrapper()); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (savedInstanceState == null) { + //noinspection ConstantConditions + setCrumb(new BreadCrumbLayout.Crumb( + FileUtil.safeGetCanonicalFile((File) getArguments().getSerializable(PATH))), true); + } else { + breadCrumbs.restoreFromStateWrapper(savedInstanceState.getParcelable(CRUMBS)); + getLoaderManager().initLoader(LOADER_ID, null, this); + } + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_folder, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + setStatusbarColorAuto(view); + getMainActivity().getSlidingUpPanelLayout().setShadowHeight(0); + + getMainActivity().setBottomBarVisibility(View.GONE); + + setUpAppbarColor(); + setUpToolbar(); + setUpBreadCrumbs(); + setUpRecyclerView(); + setUpAdapter(); + ViewUtil.setStatusBarHeight(getContext(), statusBar); + } + + private void setUpAppbarColor() { + //noinspection ConstantConditions + int primaryColor = ThemeStore.primaryColor(getActivity()); + appbar.setBackgroundColor(primaryColor); + toolbar.setBackgroundColor(primaryColor); + //breadCrumbs.setBackgroundColor(primaryColor); + breadCrumbs.setActivatedContentColor(ToolbarContentTintHelper.toolbarTitleColor(getActivity(), primaryColor)); + breadCrumbs.setDeactivatedContentColor(ToolbarContentTintHelper.toolbarSubtitleColor(getActivity(), primaryColor)); + appbar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> getMainActivity().setLightStatusbar(!ATHUtil.isWindowBackgroundDark(getContext()))); + } + + private void setUpToolbar() { + toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp); + //noinspection ConstantConditions + getActivity().setTitle(R.string.folders); + getMainActivity().setSupportActionBar(toolbar); + } + + private void setUpBreadCrumbs() { + breadCrumbs.setCallback(this); + } + + private void setUpRecyclerView() { + //noinspection ConstantConditions + ViewUtil.setUpFastScrollRecyclerViewColor(getActivity(), recyclerView, ThemeStore.accentColor(getActivity())); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + appbar.addOnOffsetChangedListener(this); + } + + private void setUpAdapter() { + adapter = new SongFileAdapter(getMainActivity(), new LinkedList(), R.layout.item_list, this, this); + adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { + @Override + public void onChanged() { + super.onChanged(); + checkIsEmpty(); + } + }); + recyclerView.setAdapter(adapter); + checkIsEmpty(); + } + + @Override + public void onPause() { + super.onPause(); + saveScrollPosition(); + } + + @Override + public void onDestroyView() { + appbar.removeOnOffsetChangedListener(this); + unbinder.unbind(); + super.onDestroyView(); + } + + @Override + public boolean handleBackPress() { + if (cab != null && cab.isActive()) { + cab.finish(); + return true; + } + if (breadCrumbs != null && breadCrumbs.popHistory()) { + setCrumb(breadCrumbs.lastHistory(), false); + return true; + } + return false; + } + + @NonNull + @Override + public MaterialCab openCab(int menuRes, MaterialCab.Callback callback) { + if (cab != null && cab.isActive()) cab.finish(); + cab = new MaterialCab(getMainActivity(), R.id.cab_stub) + .setMenu(menuRes) + .setCloseDrawableRes(R.drawable.ic_close_white_24dp) + .setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor + (getActivity()))) + .start(callback); + return cab; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.menu_folders, menu); + ToolbarContentTintHelper.handleOnCreateOptionsMenu(getActivity(), toolbar, menu, ATHToolbarActivity.getToolbarBackgroundColor(toolbar)); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + ToolbarContentTintHelper.handleOnPrepareOptionsMenu(getActivity(), toolbar); + } + + @Override + public void onCrumbSelection(BreadCrumbLayout.Crumb crumb, int index) { + setCrumb(crumb, true); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + //noinspection ConstantConditions + getActivity().onBackPressed(); + break; + case R.id.action_go_to_start_directory: + setCrumb(new BreadCrumbLayout.Crumb(tryGetCanonicalFile(PreferenceUtil.getInstance(getActivity()).getStartDirectory())), true); + return true; + case R.id.action_scan: + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null) { + //noinspection Convert2MethodRef + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(crumb.getFile(), AUDIO_FILE_FILTER)); + } + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onFileSelected(File file) { + file = tryGetCanonicalFile(file); // important as we compare the path value later + if (file.isDirectory()) { + setCrumb(new BreadCrumbLayout.Crumb(file), true); + } else { + FileFilter fileFilter = pathname -> !pathname.isDirectory() && AUDIO_FILE_FILTER.accept(pathname); + new ListSongsAsyncTask(getActivity(), file, (songs, extra) -> { + File file1 = (File) extra; + int startIndex = -1; + for (int i = 0; i < songs.size(); i++) { + if (file1.getPath().equals(songs.get(i).data)) { // path is already canonical here + startIndex = i; + break; + } + } + if (startIndex > -1) { + MusicPlayerRemote.openQueue(songs, startIndex, true); + } else { + final File finalFile = file1; + Snackbar.make(coordinatorLayout, Html.fromHtml(String.format(getString(R.string.not_listed_in_media_store), file1.getName())), Snackbar.LENGTH_LONG) + .setAction(R.string.action_scan, v -> new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(finalFile, AUDIO_FILE_FILTER))) + .setActionTextColor(ThemeStore.accentColor(getActivity())) + .show(); + } + }).execute(new ListSongsAsyncTask.LoadingInfo(toList(file.getParentFile()), fileFilter, getFileComparator())); + } + } + + @Override + public void onMultipleItemAction(MenuItem item, ArrayList files) { + final int itemId = item.getItemId(); + new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId)).execute(new ListSongsAsyncTask.LoadingInfo(files, AUDIO_FILE_FILTER, getFileComparator())); + } + + private ArrayList toList(File file) { + ArrayList files = new ArrayList<>(1); + files.add(file); + return files; + } + + private Comparator getFileComparator() { + return fileComparator; + } + + @Override + public void onFileMenuClicked(final File file, View view) { + PopupMenu popupMenu = new PopupMenu(getActivity(), view); + if (file.isDirectory()) { + popupMenu.inflate(R.menu.menu_item_directory); + popupMenu.setOnMenuItemClickListener(item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_delete_from_device: + new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId)).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_set_as_start_directory: + PreferenceUtil.getInstance(getActivity()).setStartDirectory(file); + Toast.makeText(getActivity(), String.format(getString(R.string.new_start_directory), file.getPath()), Toast.LENGTH_SHORT).show(); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; + } + return false; + }); + } else { + popupMenu.inflate(R.menu.menu_item_file); + popupMenu.setOnMenuItemClickListener(item -> { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_go_to_album: + case R.id.action_go_to_artist: + case R.id.action_share: + case R.id.action_tag_editor: + case R.id.action_details: + case R.id.action_set_as_ringtone: + case R.id.action_delete_from_device: + new ListSongsAsyncTask(getActivity(), null, (songs, extra) -> SongMenuHelper.handleMenuClick(getActivity(), songs.get(0), itemId)).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), AUDIO_FILE_FILTER, getFileComparator())); + return true; + case R.id.action_scan: + new ListPathsAsyncTask(getActivity(), paths -> scanPaths(paths)).execute(new ListPathsAsyncTask.LoadingInfo(file, AUDIO_FILE_FILTER)); + return true; + } + return false; + }); + } + popupMenu.show(); + } + + @Override + public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { + container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), container.getPaddingRight(), appbar.getTotalScrollRange() + verticalOffset); + } + + private void checkIsEmpty() { + if (empty != null) { + empty.setVisibility(adapter == null || adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + } + + private void scanPaths(@Nullable String[] toBeScanned) { + if (getActivity() == null) return; + if (toBeScanned == null || toBeScanned.length < 1) { + Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); + } else { + MediaScannerConnection.scanFile(getActivity().getApplicationContext(), toBeScanned, null, new UpdateToastMediaScannerCompletionListener(getActivity(), toBeScanned)); + } + } + + private void updateAdapter(@NonNull List files) { + adapter.swapDataSet(files); + BreadCrumbLayout.Crumb crumb = getActiveCrumb(); + if (crumb != null && recyclerView != null) { + ((LinearLayoutManager) recyclerView.getLayoutManager()) + .scrollToPositionWithOffset(crumb.getScrollPosition(), 0); + } + } + + @NonNull + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new AsyncFileLoader(this); + } + + @Override + public void onLoadFinished(@NonNull Loader> loader, List data) { + updateAdapter(data); + } + + @Override + public void onLoaderReset(@NonNull Loader> loader) { + updateAdapter(new LinkedList()); + } + + private static class AsyncFileLoader extends WrappedAsyncTaskLoader> { + private WeakReference fragmentWeakReference; + + public AsyncFileLoader(FoldersFragment foldersFragment) { + super(foldersFragment.getActivity()); + fragmentWeakReference = new WeakReference<>(foldersFragment); + } + + @Override + public List loadInBackground() { + FoldersFragment foldersFragment = fragmentWeakReference.get(); + File directory = null; + if (foldersFragment != null) { + BreadCrumbLayout.Crumb crumb = foldersFragment.getActiveCrumb(); + if (crumb != null) { + directory = crumb.getFile(); + } + } + if (directory != null) { + List files = FileUtil.listFiles(directory, AUDIO_FILE_FILTER); + Collections.sort(files, foldersFragment.getFileComparator()); + return files; + } else { + return new LinkedList<>(); + } + } + } + + private static class ListSongsAsyncTask extends ListingFilesDialogAsyncTask> { + private final Object extra; + private WeakReference contextWeakReference; + private WeakReference callbackWeakReference; + + public ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { + super(context); + this.extra = extra; + contextWeakReference = new WeakReference<>(context); + callbackWeakReference = new WeakReference<>(callback); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + checkCallbackReference(); + checkContextReference(); + } + + @Override + protected ArrayList doInBackground(LoadingInfo... params) { + try { + LoadingInfo info = params[0]; + List files = FileUtil.listFilesDeep(info.files, info.fileFilter); + + if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null) + return null; + + Collections.sort(files, info.fileComparator); + + Context context = checkContextReference(); + if (isCancelled() || context == null || checkCallbackReference() == null) + return null; + + return FileUtil.matchFilesWithMediaStore(context, files).blockingFirst(); + } catch (Exception e) { + e.printStackTrace(); + cancel(false); + return null; + } + } + + @Override + protected void onPostExecute(ArrayList songs) { + super.onPostExecute(songs); + OnSongsListedCallback callback = checkCallbackReference(); + if (songs != null && callback != null) + callback.onSongsListed(songs, extra); + } + + private Context checkContextReference() { + Context context = contextWeakReference.get(); + if (context == null) { + cancel(false); + } + return context; + } + + private OnSongsListedCallback checkCallbackReference() { + OnSongsListedCallback callback = callbackWeakReference.get(); + if (callback == null) { + cancel(false); + } + return callback; + } + + public interface OnSongsListedCallback { + void onSongsListed(@NonNull ArrayList songs, Object extra); + } + + public static class LoadingInfo { + public final Comparator fileComparator; + public final FileFilter fileFilter; + public final List files; + + public LoadingInfo(@NonNull List files, @NonNull FileFilter fileFilter, @NonNull Comparator fileComparator) { + this.fileComparator = fileComparator; + this.fileFilter = fileFilter; + this.files = files; + } + } + } + + public static class ListPathsAsyncTask extends ListingFilesDialogAsyncTask { + private WeakReference onPathsListedCallbackWeakReference; + + public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { + super(context); + onPathsListedCallbackWeakReference = new WeakReference<>(callback); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + checkCallbackReference(); + } + + @Override + protected String[] doInBackground(LoadingInfo... params) { + try { + if (isCancelled() || checkCallbackReference() == null) return null; + + LoadingInfo info = params[0]; + + final String[] paths; + + if (info.file.isDirectory()) { + List files = FileUtil.listFilesDeep(info.file, info.fileFilter); + + if (isCancelled() || checkCallbackReference() == null) return null; + + paths = new String[files.size()]; + for (int i = 0; i < files.size(); i++) { + File f = files.get(i); + paths[i] = FileUtil.safeGetCanonicalPath(f); + + if (isCancelled() || checkCallbackReference() == null) return null; + } + } else { + paths = new String[1]; + paths[0] = info.file.getPath(); + } + + return paths; + } catch (Exception e) { + e.printStackTrace(); + cancel(false); + return null; + } + } + + @Override + protected void onPostExecute(String[] paths) { + super.onPostExecute(paths); + OnPathsListedCallback callback = checkCallbackReference(); + if (callback != null && paths != null) { + callback.onPathsListed(paths); + } + } + + private OnPathsListedCallback checkCallbackReference() { + OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get(); + if (callback == null) { + cancel(false); + } + return callback; + } + + public interface OnPathsListedCallback { + void onPathsListed(@NonNull String[] paths); + } + + public static class LoadingInfo { + public final File file; + public final FileFilter fileFilter; + + public LoadingInfo(File file, FileFilter fileFilter) { + this.file = file; + this.fileFilter = fileFilter; + } + } + } + + private static abstract class ListingFilesDialogAsyncTask extends DialogAsyncTask { + public ListingFilesDialogAsyncTask(Context context) { + super(context); + } + + public ListingFilesDialogAsyncTask(Context context, int showDelay) { + super(context, showDelay); + } + + @Override + protected Dialog createDialog(@NonNull Context context) { + return new MaterialDialog.Builder(context) + .title(R.string.listing_files) + .progress(true, 0) + .progressIndeterminateStyle(true) + .cancelListener(dialog -> cancel(false)) + .dismissListener(dialog -> cancel(false)) + .negativeText(android.R.string.cancel) + .onNegative((dialog, which) -> cancel(false)) + .show(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/home/HomeFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/home/HomeFragment.java new file mode 100644 index 00000000..f9085a3e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/mainactivity/home/HomeFragment.java @@ -0,0 +1,413 @@ +package code.name.monkey.retromusic.ui.fragments.mainactivity.home; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CollapsingToolbarLayout; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; + +import java.io.File; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Random; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.dialogs.HomeOptionDialog; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.misc.AppBarStateChangeListener; +import code.name.monkey.retromusic.model.Album; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.smartplaylist.HistoryPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist; +import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist; +import code.name.monkey.retromusic.mvp.contract.HomeContract; +import code.name.monkey.retromusic.mvp.presenter.HomePresenter; +import code.name.monkey.retromusic.ui.adapter.GenreAdapter; +import code.name.monkey.retromusic.ui.adapter.album.AlbumFullWithAdapter; +import code.name.monkey.retromusic.ui.adapter.artist.ArtistAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsMainActivityFragment; +import code.name.monkey.retromusic.util.Compressor; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.RetroUtil; +import code.name.monkey.retromusic.views.CircularImageView; +import code.name.monkey.retromusic.views.MetalRecyclerViewPager; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + +import static code.name.monkey.retromusic.Constants.USER_BANNER; +import static code.name.monkey.retromusic.Constants.USER_PROFILE; + +public class HomeFragment extends AbsMainActivityFragment implements MainActivityFragmentCallbacks, + HomeContract.HomeView { + private static final String TAG = "HomeFragment"; + Unbinder unbinder; + @BindView(R.id.home_toolbar) + Toolbar toolbar; + @BindView(R.id.appbar) + AppBarLayout appbar; + @BindView(R.id.image) + ImageView imageView; + @BindView(R.id.user_image) + CircularImageView userImage; + @BindView(R.id.collapsing_toolbar) + CollapsingToolbarLayout toolbarLayout; + @BindView(R.id.recycler_view) + RecyclerView recentArtistRV; + @BindView(R.id.recent_album) + RecyclerView recentAlbumRV; + @BindView(R.id.top_artist) + RecyclerView topArtistRV; + @BindView(R.id.top_album) + MetalRecyclerViewPager topAlbumRV; + @BindView(R.id.recent_artist_container) + View recentArtistContainer; + @BindView(R.id.recent_albums_container) + View recentAlbumsContainer; + @BindView(R.id.top_artist_container) + View topArtistContainer; + @BindView(R.id.top_albums_container) + View topAlbumContainer; + @BindView(R.id.genres) + RecyclerView genresRecyclerView; + @BindView(R.id.genre_container) + LinearLayout genreContainer; + @BindView(R.id.container) + View container; + @BindView(R.id.title) + TextView title; + @BindView(R.id.search) + ImageView search; + + + private HomePresenter homePresenter; + private CompositeDisposable disposable; + + public static HomeFragment newInstance() { + Bundle args = new Bundle(); + HomeFragment fragment = new HomeFragment(); + fragment.setArguments(args); + return fragment; + } + + private void getTimeOfTheDay() { + Calendar c = Calendar.getInstance(); + int timeOfDay = c.get(Calendar.HOUR_OF_DAY); + + String[] images = new String[]{}; + if (timeOfDay >= 0 && timeOfDay < 6) { + images = getResources().getStringArray(R.array.night); + } else if (timeOfDay >= 6 && timeOfDay < 12) { + images = getResources().getStringArray(R.array.morning); + } else if (timeOfDay >= 12 && timeOfDay < 16) { + images = getResources().getStringArray(R.array.after_noon); + } else if (timeOfDay >= 16 && timeOfDay < 20) { + images = getResources().getStringArray(R.array.evening); + } else if (timeOfDay >= 20 && timeOfDay < 24) { + images = getResources().getStringArray(R.array.night); + } + String day = images[new Random().nextInt(images.length)]; + loadTimeImage(day); + } + + private void loadTimeImage(String day) { + //noinspection ConstantConditions + if (PreferenceUtil.getInstance(getActivity()).getBannerImage().isEmpty()) { + if (imageView != null) { + Glide.with(getActivity()).load(day) + .asBitmap() + .placeholder(R.drawable.material_design_default) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .into(imageView); + } + } else { + loadBannerFromStorage(); + } + } + + private void loadBannerFromStorage() { + //noinspection ConstantConditions + disposable.add(new Compressor(getContext()) + .setQuality(100) + .setCompressFormat(Bitmap.CompressFormat.WEBP) + .compressToBitmapAsFlowable( + new File(PreferenceUtil.getInstance(getContext()).getBannerImage(), USER_BANNER)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(imageView::setImageBitmap)); + } + + private void loadImageFromStorage(ImageView imageView) { + //noinspection ConstantConditions + disposable.add(new Compressor(getContext()) + .setMaxHeight(300) + .setMaxWidth(300) + .setQuality(75) + .setCompressFormat(Bitmap.CompressFormat.WEBP) + .compressToBitmapAsFlowable( + new File(PreferenceUtil.getInstance(getContext()).getProfileImage(), USER_PROFILE)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(imageView::setImageBitmap, + throwable -> imageView.setImageDrawable(ContextCompat + .getDrawable(getContext(), R.drawable.ic_person_flat)))); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + disposable = new CompositeDisposable(); + //noinspection ConstantConditions + homePresenter = new HomePresenter(this); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_home, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + getMainActivity().getSlidingUpPanelLayout().setShadowHeight(8); + getMainActivity().setBottomBarVisibility(View.VISIBLE); + + setupToolbar(); + loadImageFromStorage(userImage); + + homePresenter.subscribe(); + checkPadding(); + getTimeOfTheDay(); + } + + @SuppressWarnings("ConstantConditions") + private void setupToolbar() { + if (!PreferenceUtil.getInstance(getContext()).getFullScreenMode()) { + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar + .getLayoutParams(); + params.topMargin = RetroUtil.getStatusBarHeight(getContext()); + toolbar.setLayoutParams(params); + } + + appbar.addOnOffsetChangedListener(new AppBarStateChangeListener() { + @Override + public void onStateChanged(AppBarLayout appBarLayout, State state) { + int color; + switch (state) { + case COLLAPSED: + getMainActivity().setLightStatusbar(!ATHUtil.isWindowBackgroundDark(getContext())); + color = ThemeStore.textColorPrimary(getContext()); + break; + default: + case EXPANDED: + case IDLE: + getMainActivity().setLightStatusbar(false); + color = ContextCompat.getColor(getContext(), R.color.md_white_1000); + break; + } + TintHelper.setTintAuto(search, color, false); + title.setTextColor(color); + } + }); + + int primaryColor = ThemeStore.primaryColor(getContext()); + + TintHelper.setTintAuto(container, primaryColor, true); + toolbarLayout.setStatusBarScrimColor(primaryColor); + toolbarLayout.setContentScrimColor(primaryColor); + + toolbar.setTitle(R.string.home); + getMainActivity().setSupportActionBar(toolbar); + + } + + @Override + public boolean handleBackPress() { + return false; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + disposable.clear(); + homePresenter.unsubscribe(); + } + + @Override + public void loading() { + + } + + @Override + public void showEmptyView() { + + } + + @Override + public void completed() { + + } + + @Override + public void showData(ArrayList homes) { + //homeAdapter.swapDataSet(homes); + } + + @Override + public void onMediaStoreChanged() { + super.onMediaStoreChanged(); + homePresenter.subscribe(); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + checkPadding(); + } + + @Override + public void onQueueChanged() { + super.onQueueChanged(); + checkPadding(); + } + + private void checkPadding() { + int height = getResources().getDimensionPixelSize(R.dimen.mini_player_height); + container.setPadding(0, 0, 0, MusicPlayerRemote.getPlayingQueue().isEmpty() ? height * 2 : 0); + } + + @Override + public void recentArtist(ArrayList artists) { + recentArtistContainer.setVisibility(View.VISIBLE); + recentArtistRV.setLayoutManager(new GridLayoutManager(getMainActivity(), + 1, GridLayoutManager.HORIZONTAL, false)); + ArtistAdapter artistAdapter = new ArtistAdapter(getMainActivity(), artists, + R.layout.item_artist, false, null); + recentArtistRV.setAdapter(artistAdapter); + } + + @Override + public void recentAlbum(ArrayList albums) { + recentAlbumsContainer.setVisibility(View.VISIBLE); + AlbumFullWithAdapter artistAdapter = new AlbumFullWithAdapter(getMainActivity(), + getDisplayMetrics()); + artistAdapter.swapData(albums); + recentAlbumRV.setAdapter(artistAdapter); + } + + @Override + public void topArtists(ArrayList artists) { + topArtistContainer.setVisibility(View.VISIBLE); + topArtistRV.setLayoutManager(new GridLayoutManager(getMainActivity(), + 1, GridLayoutManager.HORIZONTAL, false)); + ArtistAdapter artistAdapter = new ArtistAdapter(getMainActivity(), artists, + R.layout.item_artist, false, null); + topArtistRV.setAdapter(artistAdapter); + + } + + @Override + public void topAlbums(ArrayList albums) { + topAlbumContainer.setVisibility(View.VISIBLE); + AlbumFullWithAdapter artistAdapter = new AlbumFullWithAdapter(getMainActivity(), + getDisplayMetrics()); + artistAdapter.swapData(albums); + topAlbumRV.setAdapter(artistAdapter); + } + + private DisplayMetrics getDisplayMetrics() { + Display display = getMainActivity().getWindowManager().getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + + return metrics; + } + + @Override + public void suggestions(ArrayList playlists) { + + } + + + @Override + public void geners(ArrayList genres) { + genreContainer.setVisibility(View.VISIBLE); + genresRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + //noinspection ConstantConditions + GenreAdapter genreAdapter = new GenreAdapter(getActivity(), genres, R.layout.item_list); + genresRecyclerView.setAdapter(genreAdapter); + } + + + @OnClick({R.id.last_added, R.id.top_played, R.id.action_shuffle, R.id.history, + R.id.user_image, R.id.search}) + void startUserInfo(View view) { + Activity activity = getActivity(); + if (activity != null) { + switch (view.getId()) { + case R.id.action_shuffle: + MusicPlayerRemote + .openAndShuffleQueue(SongLoader.getAllSongs(activity).blockingFirst(), true); + break; + case R.id.last_added: + NavigationUtil.goToPlaylistNew(activity, new LastAddedPlaylist(activity)); + break; + case R.id.top_played: + NavigationUtil.goToPlaylistNew(activity, new MyTopTracksPlaylist(activity)); + break; + case R.id.history: + NavigationUtil.goToPlaylistNew(activity, new HistoryPlaylist(activity)); + break; + case R.id.search: + NavigationUtil.goToSearch(activity); + break; + case R.id.user_image: + new HomeOptionDialog().show(getFragmentManager(), TAG); + break; + } + } + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + homePresenter.loadRecentArtists(); + homePresenter.loadRecentAlbums(); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/PlayerAlbumCoverFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/PlayerAlbumCoverFragment.java new file mode 100644 index 00000000..e5ff394f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/PlayerAlbumCoverFragment.java @@ -0,0 +1,154 @@ +package code.name.monkey.retromusic.ui.fragments.player; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.transform.CustPagerTransformer; +import code.name.monkey.retromusic.transform.NormalPageTransformer; +import code.name.monkey.retromusic.transform.ParallaxPagerTransformer; +import code.name.monkey.retromusic.ui.adapter.album.AlbumCoverPagerAdapter; +import code.name.monkey.retromusic.ui.fragments.NowPlayingScreen; +import code.name.monkey.retromusic.ui.fragments.base.AbsMusicServiceFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; + + +public class PlayerAlbumCoverFragment extends AbsMusicServiceFragment implements + ViewPager.OnPageChangeListener { + + public static final String TAG = PlayerAlbumCoverFragment.class.getSimpleName(); + public static final long VISIBILITY_ANIM_DURATION = 300; + @BindView(R.id.player_album_cover_viewpager) + ViewPager viewPager; + private Unbinder unbinder; + private Callbacks callbacks; + private int currentPosition; + private AlbumCoverPagerAdapter.AlbumCoverFragment.ColorReceiver colorReceiver = + new AlbumCoverPagerAdapter.AlbumCoverFragment.ColorReceiver() { + @Override + public void onColorReady(int color, int requestCode) { + if (currentPosition == requestCode) { + notifyColorChange(color); + } + } + }; + + public void removeSlideEffect() { + ParallaxPagerTransformer transformer = new ParallaxPagerTransformer(R.id.player_image); + transformer.setSpeed(0.3f); + viewPager.setPageTransformer(true, transformer); + + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_player_album_cover, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewPager.addOnPageChangeListener(this); + + //noinspection ConstantConditions + if (PreferenceUtil.getInstance(getContext()).carouselEffect() && !( + (PreferenceUtil.getInstance(getContext()).getNowPlayingScreen() == NowPlayingScreen.FULL) || + (PreferenceUtil.getInstance(getContext()).getNowPlayingScreen() + == NowPlayingScreen.FLAT))) { + viewPager.setClipToPadding(false); + viewPager.setPadding(96, 0, 96, 0); + viewPager.setPageMargin(18); + + viewPager.setPageTransformer(false, new CustPagerTransformer(getContext())); + } else { + viewPager.setPageTransformer(true, new NormalPageTransformer()); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + viewPager.removeOnPageChangeListener(this); + unbinder.unbind(); + } + + @Override + public void onServiceConnected() { + updatePlayingQueue(); + } + + @Override + public void onPlayingMetaChanged() { + viewPager.setCurrentItem(MusicPlayerRemote.getPosition()); + } + + @Override + public void onQueueChanged() { + updatePlayingQueue(); + } + + private void updatePlayingQueue() { + viewPager.setAdapter( + new AlbumCoverPagerAdapter(getFragmentManager(), MusicPlayerRemote.getPlayingQueue())); + //noinspection ConstantConditions + viewPager.getAdapter().notifyDataSetChanged(); + viewPager.setCurrentItem(MusicPlayerRemote.getPosition()); + onPageSelected(MusicPlayerRemote.getPosition()); + + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + currentPosition = position; + if (viewPager.getAdapter() != null) { + ((AlbumCoverPagerAdapter) viewPager.getAdapter()).receiveColor(colorReceiver, position); + } + if (position != MusicPlayerRemote.getPosition()) { + MusicPlayerRemote.playSongAt(position); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + + + private void notifyColorChange(int color) { + if (callbacks != null) { + callbacks.onColorChanged(color); + } + } + + public void setCallbacks(Callbacks listener) { + callbacks = listener; + } + + public void removeEffect() { + viewPager.setPageTransformer(false, null); + } + + + public interface Callbacks { + + void onColorChanged(int color); + + void onFavoriteToggled(); + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptiveFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptiveFragment.java new file mode 100644 index 00000000..482a93e5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptiveFragment.java @@ -0,0 +1,165 @@ +package code.name.monkey.retromusic.ui.fragments.player.adaptive; + +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment.Callbacks; +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment; + +public class AdaptiveFragment extends AbsPlayerFragment implements Callbacks { + + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.status_bar) + View statusBar; + + private int lastColor; + private AdaptivePlaybackControlsFragment playbackControlsFragment; + private Unbinder unbinder; + + public static PlayerFragment newInstance() { + Bundle args = new Bundle(); + PlayerFragment fragment = new PlayerFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + @ColorInt + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + playbackControlsFragment.show(); + } + + @Override + public void onHide() { + playbackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return ATHUtil.resolveColor(getContext(), R.attr.iconColor); + } + + @Override + public void onColorChanged(int color) { + playbackControlsFragment.setDark(color); + lastColor = color; + getCallbacks().onPaletteColorChanged(); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_adaptive_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + playbackControlsFragment = + (AdaptivePlaybackControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = + (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + playerAlbumCoverFragment.removeSlideEffect(); + } + + private void setUpPlayerToolbar() { + int primaryColor = ATHUtil.resolveColor(getContext(), R.attr.iconColor); + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, primaryColor, getActivity()); + toolbar.setTitleTextColor(primaryColor); + toolbar.setSubtitleTextColor(ThemeStore.textColorSecondary(getContext())); + } + + @Override + public void onServiceConnected() { + updateIsFavorite(); + updateSong(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + toolbar.setTitle(song.title); + toolbar.setSubtitle(song.artistName); + } + + @Override + public void onPlayingMetaChanged() { + updateIsFavorite(); + updateSong(); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptivePlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptivePlaybackControlsFragment.java new file mode 100644 index 00000000..4db21270 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/adaptive/AdaptivePlaybackControlsFragment.java @@ -0,0 +1,313 @@ +package code.name.monkey.retromusic.ui.fragments.player.adaptive; + +import android.animation.ObjectAnimator; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatSeekBar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +public class AdaptivePlaybackControlsFragment extends AbsPlayerControlsFragment { + + @BindView(R.id.player_play_pause_button) + ImageButton playPauseButton; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.volume_fragment_container) + View volumeContainer; + + + private Unbinder unbinder; + private PlayPauseDrawable playPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater + .inflate(R.layout.fragment_adaptive_player_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + unbinder = ButterKnife.bind(this, view); + setUpMusicControllers(); + + hideVolumeIfAvailable(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void setDark(int dark) { + + if (ColorUtil.isColorLight(ATHUtil.resolveColor(getContext(), android.R.attr.windowBackground))) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false); + } + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + updatePlayPauseColor(); + + TintHelper.setTintAuto(playPauseButton, MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(dark)), false); + TintHelper.setTintAuto(playPauseButton, dark, true); + TintHelper.setTintAuto(progressSlider, dark, false); + } + + private void updatePlayPauseColor() { + //playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpPlayPauseButton() { + playPauseDrawable = new PlayPauseDrawable(getActivity()); + playPauseButton.setImageDrawable(playPauseDrawable); + updatePlayPauseColor(); + playPauseButton.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseButton.post(() -> { + if (playPauseButton != null) { + playPauseButton.setPivotX(playPauseButton.getWidth() / 2); + playPauseButton.setPivotY(playPauseButton.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playPauseDrawable.setPause(animate); + } else { + playPauseDrawable.setPlay(animate); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseButton(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + + @Override + protected void show() { + //Ignore + } + + @Override + protected void hide() { + //Ignore + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + + + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), + MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + public void showBouceAnimation() { + playPauseButton.clearAnimation(); + + playPauseButton.setScaleX(0.9f); + playPauseButton.setScaleY(0.9f); + playPauseButton.setVisibility(View.VISIBLE); + playPauseButton.setPivotX(playPauseButton.getWidth() / 2); + playPauseButton.setPivotY(playPauseButton.getHeight() / 2); + + playPauseButton.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseButton.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } + + public void hideVolumeIfAvailable() { + volumeContainer.setVisibility( + PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlaybackControlsFragment.java new file mode 100644 index 00000000..f8a337ae --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlaybackControlsFragment.java @@ -0,0 +1,301 @@ +package code.name.monkey.retromusic.ui.fragments.player.blur; + +import android.animation.ObjectAnimator; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.AppCompatSeekBar; +import android.support.v7.widget.AppCompatTextView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.VolumeFragment; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + + +public class BlurPlaybackControlsFragment extends AbsPlayerControlsFragment { + + @BindView(R.id.player_play_pause_button) + ImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.title) + AppCompatTextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.volume_fragment_container) + View mVolumeContainer; + private Unbinder unbinder; + private PlayPauseDrawable playerFabPlayPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_blur_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + unbinder = ButterKnife.bind(this, view); + setUpMusicControllers(); + + mVolumeContainer.setVisibility(PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + + VolumeFragment mVolumeFragment = (VolumeFragment) getChildFragmentManager().findFragmentById(R.id.volume_fragment); + mVolumeFragment.tintWhiteColor(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void setDark(int dark) { + lastPlaybackControlsColor = Color.WHITE; + lastDisabledPlaybackControlsColor = ContextCompat.getColor(getContext(), R.color.md_grey_500); + + setProgressBarColor(Color.WHITE); + + songCurrentProgress.setTextColor(lastPlaybackControlsColor); + songTotalTime.setTextColor(lastPlaybackControlsColor); + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + } + + public void setProgressBarColor(int newColor) { + TintHelper.setTintAuto(progressSlider, newColor, false); + } + + private void setUpPlayPauseFab() { + final int fabColor = Color.WHITE; + TintHelper.setTintAuto(playPauseFab, fabColor, true); + + playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + + playPauseFab.setImageDrawable(playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called + playPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(fabColor)), PorterDuff.Mode.SRC_IN); + playPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseFab.post(() -> { + if (playPauseFab != null) { + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playerFabPlayPauseDrawable.setPause(animate); + } else { + playerFabPlayPauseDrawable.setPlay(animate); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + + @Override + protected void show() { + playPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + @Override + protected void hide() { + if (playPauseFab != null) { + playPauseFab.setScaleX(0f); + playPauseFab.setScaleY(0f); + playPauseFab.setRotation(0f); + } + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } + + public void hideVolumeIfAvailable() { + mVolumeContainer.setVisibility(View.GONE); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlayerFragment.java new file mode 100644 index 00000000..ddd9ed35 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/blur/BlurPlayerFragment.java @@ -0,0 +1,315 @@ +package code.name.monkey.retromusic.ui.fragments.player.blur; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager; +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.adapter.song.PlayingQueueAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment; +import jp.wasabeef.glide.transformations.BlurTransformation; + +/** + * @author Hemanth S (h4h13). + */ + +public class BlurPlayerFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks { + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.toolbar_container) + View toolbarContainer; + @BindView(R.id.gradient_background) + ImageView colorBackground; + @BindView(R.id.status_bar) + View statusBar; + @Nullable + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + @Nullable + @BindView(R.id.title) + TextView title; + + private int lastColor; + private BlurPlaybackControlsFragment playbackControlsFragment; + private Unbinder unbinder; + + private RecyclerView.Adapter wrappedAdapter; + private RecyclerViewDragDropManager recyclerViewDragDropManager; + private PlayingQueueAdapter playingQueueAdapter; + private LinearLayoutManager layoutManager; + + public static PlayerFragment newInstance() { + Bundle args = new Bundle(); + PlayerFragment fragment = new PlayerFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + @ColorInt + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + playbackControlsFragment.show(); + } + + @Override + public void onHide() { + playbackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return Color.WHITE; + } + + @Override + public void onColorChanged(int color) { + playbackControlsFragment.setDark(color); + lastColor = color; + getCallbacks().onPaletteColorChanged(); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, Color.WHITE, getActivity()); + + if (title != null && playingQueueAdapter != null) { + if (ColorUtil.isColorLight(color)) { + title.setTextColor(Color.BLACK); + playingQueueAdapter.usePalette(false); + } else { + title.setTextColor(Color.WHITE); + playingQueueAdapter.usePalette(true); + } + } + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager.release(); + recyclerViewDragDropManager = null; + } + + if (recyclerView != null) { + recyclerView.setItemAnimator(null); + recyclerView.setAdapter(null); + recyclerView = null; + } + + if (wrappedAdapter != null) { + WrapperAdapterUtils.releaseAll(wrappedAdapter); + wrappedAdapter = null; + } + playingQueueAdapter = null; + layoutManager = null; + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_blur, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + playbackControlsFragment = (BlurPlaybackControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = + (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, Color.WHITE, getActivity()); + } + + private void updateBlur() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + int blurAmount = PreferenceManager.getDefaultSharedPreferences(getContext()).getInt("blur_amount", 25); + + colorBackground.clearColorFilter(); + + SongGlideRequest.Builder.from(Glide.with(activity), MusicPlayerRemote.getCurrentSong()) + .checkIgnoreMediaStore(activity) + .generatePalette(activity) + .build() + .override(320, 480) + .transform(new BlurTransformation(getActivity(), blurAmount)) + .into(new RetroMusicColoredTarget(colorBackground) { + @Override + public void onColorReady(int color) { + if (color == getDefaultFooterColor()) { + colorBackground.setColorFilter(color); + } + } + }); + } + + @Override + public void onServiceConnected() { + updateIsFavorite(); + updateBlur(); + setUpRecyclerView(); + } + + @Override + public void onPlayingMetaChanged() { + updateIsFavorite(); + updateBlur(); + updateQueuePosition(); + } + + private void setUpRecyclerView() { + if (recyclerView != null) { + recyclerViewDragDropManager = new RecyclerViewDragDropManager(); + final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator(); + + playingQueueAdapter = new PlayingQueueAdapter( + (AppCompatActivity) getActivity(), + MusicPlayerRemote.getPlayingQueue(), + MusicPlayerRemote.getPosition(), + R.layout.item_song, + false, + null); + wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(playingQueueAdapter); + + layoutManager = new LinearLayoutManager(getContext()); + + + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(wrappedAdapter); + recyclerView.setItemAnimator(animator); + recyclerViewDragDropManager.attachRecyclerView(recyclerView); + layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0); + } + } + + @Override + public void onQueueChanged() { + updateQueue(); + updateCurrentSong(); + } + + @Override + public void onMediaStoreChanged() { + updateQueue(); + updateCurrentSong(); + } + + @SuppressWarnings("ConstantConditions") + private void updateCurrentSong() { + } + + private void updateQueuePosition() { + if (playingQueueAdapter != null) { + playingQueueAdapter.setCurrent(MusicPlayerRemote.getPosition()); + // if (slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { + resetToCurrentPosition(); + //} + } + } + + private void updateQueue() { + if (playingQueueAdapter != null) { + playingQueueAdapter.swapDataSet(MusicPlayerRemote.getPlayingQueue(), MusicPlayerRemote.getPosition()); + resetToCurrentPosition(); + } + } + + private void resetToCurrentPosition() { + if (recyclerView != null) { + recyclerView.stopScroll(); + layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0); + } + + } + + @Override + public void onPause() { + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager.cancelDrag(); + } + super.onPause(); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/card/CardFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/card/CardFragment.java new file mode 100644 index 00000000..355e9310 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/card/CardFragment.java @@ -0,0 +1,283 @@ +package code.name.monkey.retromusic.ui.fragments.player.card; + +import android.graphics.Color; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator; +import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager; +import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.adapter.song.PlayingQueueAdapter; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment; + +public class CardFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks { + + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.status_bar) + View statusBar; + @Nullable + @BindView(R.id.recycler_view) + RecyclerView recyclerView; + @Nullable + @BindView(R.id.title) + TextView title; + private RecyclerView.Adapter wrappedAdapter; + private RecyclerViewDragDropManager recyclerViewDragDropManager; + private PlayingQueueAdapter playingQueueAdapter; + private LinearLayoutManager layoutManager; + private int lastColor; + private CardPlaybackControlsFragment playbackControlsFragment; + private Unbinder unbinder; + + public static PlayerFragment newInstance() { + Bundle args = new Bundle(); + PlayerFragment fragment = new PlayerFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + @ColorInt + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + playbackControlsFragment.show(); + } + + @Override + public void onHide() { + playbackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return Color.WHITE; + } + + @Override + public void onColorChanged(int color) { + playbackControlsFragment.setDark(color); + lastColor = color; + getCallbacks().onPaletteColorChanged(); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, Color.WHITE, getActivity()); + + if (title != null && playingQueueAdapter != null) { + if (ColorUtil.isColorLight(color)) { + title.setTextColor(Color.BLACK); + playingQueueAdapter.usePalette(false); + } else { + title.setTextColor(Color.WHITE); + playingQueueAdapter.usePalette(true); + } + } + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager.release(); + recyclerViewDragDropManager = null; + } + + if (recyclerView != null) { + recyclerView.setItemAnimator(null); + recyclerView.setAdapter(null); + recyclerView = null; + } + + if (wrappedAdapter != null) { + WrapperAdapterUtils.releaseAll(wrappedAdapter); + wrappedAdapter = null; + } + playingQueueAdapter = null; + layoutManager = null; + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_card_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpSubFragments(); + setUpPlayerToolbar(); + setUpRecyclerView(); + } + + private void setUpSubFragments() { + playbackControlsFragment = + (CardPlaybackControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + playerAlbumCoverFragment.removeSlideEffect(); + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, Color.WHITE, getActivity()); + + } + + @Override + public void onServiceConnected() { + updateIsFavorite(); + //updateLyrics(); + setUpRecyclerView(); + } + + @Override + public void onPlayingMetaChanged() { + updateIsFavorite(); + //updateLyrics(); + updateQueuePosition(); + } + + private void setUpRecyclerView() { + if (recyclerView != null) { + recyclerViewDragDropManager = new RecyclerViewDragDropManager(); + final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator(); + + playingQueueAdapter = new PlayingQueueAdapter( + (AppCompatActivity) getActivity(), + MusicPlayerRemote.getPlayingQueue(), + MusicPlayerRemote.getPosition(), + R.layout.item_song, + false, + null); + wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(playingQueueAdapter); + + layoutManager = new LinearLayoutManager(getContext()); + + recyclerView.setLayoutManager(layoutManager); + recyclerView.setAdapter(wrappedAdapter); + recyclerView.setItemAnimator(animator); + recyclerViewDragDropManager.attachRecyclerView(recyclerView); + layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0); + } + } + + @Override + public void onQueueChanged() { + updateQueue(); + updateCurrentSong(); + } + + @Override + public void onMediaStoreChanged() { + updateQueue(); + updateCurrentSong(); + } + + @SuppressWarnings("ConstantConditions") + private void updateCurrentSong() { + } + + private void updateQueuePosition() { + if (playingQueueAdapter != null) { + playingQueueAdapter.setCurrent(MusicPlayerRemote.getPosition()); + } + // if (slidingUpPanelLayout.getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) { + resetToCurrentPosition(); + //} + } + + private void updateQueue() { + if (playingQueueAdapter != null) { + playingQueueAdapter.swapDataSet(MusicPlayerRemote.getPlayingQueue(), + MusicPlayerRemote.getPosition()); + resetToCurrentPosition(); + } + } + + private void resetToCurrentPosition() { + if (recyclerView != null) { + recyclerView.stopScroll(); + layoutManager.scrollToPositionWithOffset(MusicPlayerRemote.getPosition() + 1, 0); + } + + } + + @Override + public void onPause() { + if (recyclerViewDragDropManager != null) { + recyclerViewDragDropManager.cancelDrag(); + } + + super.onPause(); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/card/CardPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/card/CardPlaybackControlsFragment.java new file mode 100644 index 00000000..e92c885b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/card/CardPlaybackControlsFragment.java @@ -0,0 +1,358 @@ +package code.name.monkey.retromusic.ui.fragments.player.card; + +import android.animation.ObjectAnimator; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatSeekBar; +import android.support.v7.widget.AppCompatTextView; +import android.support.v7.widget.CardView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +public class CardPlaybackControlsFragment extends AbsPlayerControlsFragment { + @BindView(R.id.player_play_pause_button) + ImageButton playPauseButton; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.title) + AppCompatTextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.volume_fragment_container) + View volumeContainer; + @BindView(R.id.menu) + View menuView; + @BindView(R.id.image_text_container) + CardView colorContainer; + + @BindView(R.id.image) + ImageView playImageView; + @BindView(R.id.playback_controls) + View playbackControls; + + private Unbinder unbinder; + private PlayPauseDrawable playPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_card_player_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + unbinder = ButterKnife.bind(this, view); + setUpMusicControllers(); + + hideVolumeIfAvailable(); + + setupControls(); + } + + private void setupControls() { + playImageView.setImageResource(R.drawable.ic_play_circle_filled_white_24dp); + //noinspection ConstantConditions + int iconPadding = getActivity().getResources().getDimensionPixelSize(R.dimen.list_item_image_icon_padding); + playImageView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding); + + menuView.setVisibility(View.GONE); + + int primaryColor = ThemeStore.primaryColor(getContext()); + playbackControls.setBackgroundColor(primaryColor); + colorContainer.setCardBackgroundColor(primaryColor); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void setDark(int dark) { + playImageView.setColorFilter(dark, PorterDuff.Mode.SRC_IN); + if (ColorUtil.isColorLight(ATHUtil.resolveColor(getContext(), android.R.attr.windowBackground))) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false); + } + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + updatePlayPauseColor(); + updateProgressTextColor(); + + TintHelper.setTintAuto(playPauseButton, MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(dark)), false); + TintHelper.setTintAuto(playPauseButton, dark, true); + } + + private void updatePlayPauseColor() { + //playPauseButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpPlayPauseButton() { + playPauseDrawable = new PlayPauseDrawable(getActivity()); + playPauseButton.setImageDrawable(playPauseDrawable); + updatePlayPauseColor(); + playPauseButton.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseButton.post(() -> { + if (playPauseButton != null) { + playPauseButton.setPivotX(playPauseButton.getWidth() / 2); + playPauseButton.setPivotY(playPauseButton.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playPauseDrawable.setPause(animate); + } else { + playPauseDrawable.setPlay(animate); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseButton(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void updateProgressTextColor() { + int color = MaterialValueHelper.getPrimaryTextColor(getContext(), false); + songTotalTime.setTextColor(color); + songCurrentProgress.setTextColor(color); + } + + @Override + protected void show() { + //Ignore + } + + @Override + protected void hide() { + //Ignore + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + public void showBouceAnimation() { + playPauseButton.clearAnimation(); + + playPauseButton.setScaleX(0.9f); + playPauseButton.setScaleY(0.9f); + playPauseButton.setVisibility(View.VISIBLE); + playPauseButton.setPivotX(playPauseButton.getWidth() / 2); + playPauseButton.setPivotY(playPauseButton.getHeight() / 2); + + playPauseButton.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseButton.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } + + public void hideVolumeIfAvailable() { + volumeContainer.setVisibility(PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurFragment.java new file mode 100644 index 00000000..3d6e2436 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurFragment.java @@ -0,0 +1,192 @@ +package code.name.monkey.retromusic.ui.fragments.player.cardblur; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment; +import jp.wasabeef.glide.transformations.BlurTransformation; + +public class CardBlurFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks { + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.status_bar) + View statusBar; + @BindView(R.id.gradient_background) + ImageView colorBackground; + + private int lastColor; + private CardBlurPlaybackControlsFragment playbackControlsFragment; + private Unbinder unbinder; + + public static PlayerFragment newInstance() { + Bundle args = new Bundle(); + PlayerFragment fragment = new PlayerFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + @ColorInt + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + playbackControlsFragment.show(); + } + + @Override + public void onHide() { + playbackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return Color.WHITE; + } + + @Override + public void onColorChanged(int color) { + playbackControlsFragment.setDark(color); + lastColor = color; + getCallbacks().onPaletteColorChanged(); + ToolbarContentTintHelper.colorizeToolbar(toolbar, Color.WHITE, getActivity()); + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_card_blur_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + playbackControlsFragment = (CardBlurPlaybackControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = + (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + playerAlbumCoverFragment.removeEffect(); + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + /* for (int i = 0; i < toolbar.getMenu().size(); i++) { + MenuItem menuItem = toolbar.getMenu().getItem(i); + menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + }*/ + ToolbarContentTintHelper.colorizeToolbar(toolbar, Color.WHITE, getActivity()); + } + + @Override + public void onServiceConnected() { + updateIsFavorite(); + updateBlur(); + } + + @Override + public void onPlayingMetaChanged() { + updateIsFavorite(); + updateBlur(); + } + + private void updateBlur() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + int blurAmount = PreferenceManager.getDefaultSharedPreferences(getContext()).getInt("blur_amount", 25); + + colorBackground.clearColorFilter(); + SongGlideRequest.Builder.from(Glide.with(activity), MusicPlayerRemote.getCurrentSong()) + .checkIgnoreMediaStore(activity) + .generatePalette(activity) + .build() + .transform(new BlurTransformation(getActivity(), blurAmount)) + .into(new RetroMusicColoredTarget(colorBackground) { + @Override + public void onColorReady(int color) { + if (color == getDefaultFooterColor()) { + colorBackground.setColorFilter(color); + } + } + }); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurPlaybackControlsFragment.java new file mode 100644 index 00000000..a052d909 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/cardblur/CardBlurPlaybackControlsFragment.java @@ -0,0 +1,335 @@ +package code.name.monkey.retromusic.ui.fragments.player.cardblur; + +import android.animation.ObjectAnimator; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatSeekBar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.VolumeFragment; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +public class CardBlurPlaybackControlsFragment extends AbsPlayerControlsFragment { + @BindView(R.id.player_play_pause_button) + ImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.volume_fragment_container) + View volumeContainer; + @BindView(R.id.text) + TextView text; + @BindView(R.id.title) + TextView title; + private Unbinder unbinder; + private PlayPauseDrawable playerFabPlayPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_card_blur_player_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + unbinder = ButterKnife.bind(this, view); + setUpMusicControllers(); + hideVolumeIfAvailable(); + + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateSong(); + updateShuffleState(); + + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void setDark(int dark) { + + title.setTextColor(Color.WHITE); + text.setTextColor(Color.WHITE); + + lastPlaybackControlsColor = Color.WHITE; + lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(Color.WHITE, 0.3f); + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + updateProgressTextColor(); + } + + + private void setUpPlayPauseFab() { + final int fabColor = Color.WHITE; + TintHelper.setTintAuto(playPauseFab, fabColor, true); + + playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + + playPauseFab.setImageDrawable(playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called + playPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), + ColorUtil.isColorLight(fabColor)), PorterDuff.Mode.SRC_IN); + playPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseFab.post(() -> { + if (playPauseFab != null) { + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playerFabPlayPauseDrawable.setPause(animate); + } else { + playerFabPlayPauseDrawable.setPlay(animate); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + setupVolumeControls(); + } + + private void setupVolumeControls() { + VolumeFragment volumeFragment = (VolumeFragment) getChildFragmentManager() + .findFragmentById(R.id.volume_fragment); + volumeFragment.tintWhiteColor(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void updateProgressTextColor() { + int color = MaterialValueHelper.getPrimaryTextColor(getContext(), false); + songTotalTime.setTextColor(color); + songCurrentProgress.setTextColor(color); + } + + @Override + protected void show() { + //Ignore + } + + @Override + protected void hide() { + //Ignore + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + progressSlider.setBackgroundTintList(ColorStateList.valueOf(Color.WHITE)); + } + + public void showBouceAnimation() { + playPauseFab.clearAnimation(); + + playPauseFab.setScaleX(0.9f); + playPauseFab.setScaleY(0.9f); + playPauseFab.setVisibility(View.VISIBLE); + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + + playPauseFab.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseFab.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } + + public void hideVolumeIfAvailable() { + volumeContainer.setVisibility(PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/color/ColorFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/color/ColorFragment.java new file mode 100644 index 00000000..74d10fdf --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/color/ColorFragment.java @@ -0,0 +1,305 @@ +package code.name.monkey.retromusic.ui.fragments.player.color; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.graphics.Palette; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.glide.RetroMusicColoredTarget; +import code.name.monkey.retromusic.glide.SongGlideRequest; +import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.model.lyrics.Lyrics; +import code.name.monkey.retromusic.ui.activities.LyricsActivity; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.RetroColorUtil; +import code.name.monkey.retromusic.util.ViewUtil; + +public class ColorFragment extends AbsPlayerFragment { + + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.gradient_background) + View colorBackground; + @BindView(R.id.status_bar) + View statusBar; + @BindView(R.id.image) + ImageView imageView; + @BindView(R.id.lyrics) + TextView lyricsView; + @BindView(R.id.lyrics_container) + View lyricsViewContainer; + @BindView(R.id.album_cover_container) + View imageViewContainer; + + private int lastColor; + private int backgroundColor; + private ColorPlaybackControlsFragment playbackControlsFragment; + private Unbinder unbinder; + private ValueAnimator valueAnimator; + private AsyncTask updateLyricsAsyncTask; + private Lyrics lyrics; + + public static ColorFragment newInstance() { + Bundle args = new Bundle(); + ColorFragment fragment = new ColorFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onShow() { + playbackControlsFragment.show(); + } + + @Override + public void onHide() { + playbackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + @ColorInt + public int getPaletteColor() { + return backgroundColor; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return lastColor; + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + if (valueAnimator != null) { + valueAnimator.cancel(); + valueAnimator = null; + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_color_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + playbackControlsFragment = (ColorPlaybackControlsFragment) + getChildFragmentManager().findFragmentById(R.id.playback_controls_fragment); + + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + updateLyricsLocal(); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + updateSong(); + updateLyricsLocal(); + } + + private void updateSong() { + Activity activity = getActivity(); + + SongGlideRequest.Builder.from(Glide.with(activity), MusicPlayerRemote.getCurrentSong()) + .checkIgnoreMediaStore(activity) + .generatePalette(activity).build().dontAnimate() + .into(new RetroMusicColoredTarget(imageView) { + @Override + public void onColorReady(int color) { + //setColors(color); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + + int backgroundColor = getDefaultFooterColor(); + int textColor = ColorUtil.isColorLight(getDefaultFooterColor()) ? + MaterialValueHelper.getPrimaryTextColor(getContext(), true) : + MaterialValueHelper.getPrimaryTextColor(getContext(), false); + + setColors(backgroundColor, textColor); + } + + @Override + public void onResourceReady(BitmapPaletteWrapper resource, + GlideAnimation glideAnimation) { + super.onResourceReady(resource, glideAnimation); + /* MediaNotificationProcessor processor = new MediaNotificationProcessor(getContext(), + getContext()); + Palette.Builder builder = MediaNotificationProcessor + .generatePalette(resource.getBitmap()); + + int backgroundColor = processor.getBackgroundColor(builder); + int textColor = processor.getTextColor(builder);*/ + + Palette palette = resource.getPalette(); + Palette.Swatch swatch = RetroColorUtil.getSwatch(palette); + + int textColor = RetroColorUtil.getTextColor(palette); + int backgroundColor = swatch.getRgb(); + + setColors(backgroundColor, textColor); + } + }); + } + + private void setColors(int backgroundColor, int textColor) { + playbackControlsFragment.setDark(textColor, backgroundColor); + + colorBackground.setBackgroundColor(backgroundColor); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, textColor, getActivity()); + + lastColor = textColor; + + this.backgroundColor = backgroundColor; + + getCallbacks().onPaletteColorChanged(); + } + + private void colorize(int i) { + if (valueAnimator != null) { + valueAnimator.cancel(); + } + + valueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), Color.TRANSPARENT, i); + valueAnimator.addUpdateListener(animation -> { + if (colorBackground != null) { + colorBackground.setBackgroundColor((Integer) animation.getAnimatedValue()); + } + }); + valueAnimator.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME).start(); + } + + @SuppressLint("StaticFieldLeak") + private void updateLyricsLocal() { + if (updateLyricsAsyncTask != null) { + updateLyricsAsyncTask.cancel(false); + } + final Song song = MusicPlayerRemote.getCurrentSong(); + updateLyricsAsyncTask = new AsyncTask() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + lyrics = null; + toolbar.getMenu().removeItem(R.id.action_show_lyrics); + } + + @Override + protected Lyrics doInBackground(Void... params) { + String data = MusicUtil.getLyrics(song); + if (TextUtils.isEmpty(data)) { + return null; + } + return Lyrics.parse(song, data); + } + + @Override + protected void onPostExecute(Lyrics l) { + lyrics = l; + if (lyrics == null) { + lyricsView.setText(R.string.no_lyrics_found); + } else { + lyricsView.setText(lyrics.getText()); + } + } + + @Override + protected void onCancelled(Lyrics s) { + onPostExecute(null); + } + }.execute(); + } + + @OnClick(R.id.expand) + void expand() { + startActivity(new Intent(getContext(), LyricsActivity.class)); + } + + @OnClick({R.id.lyrics, R.id.image}) + void toggleLyrics(View view) { + if (lyricsViewContainer.getVisibility() == View.GONE) { + lyricsViewContainer.setVisibility(View.VISIBLE); + } else { + lyricsViewContainer.setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/color/ColorPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/color/ColorPlaybackControlsFragment.java new file mode 100644 index 00000000..7efff6ff --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/color/ColorPlaybackControlsFragment.java @@ -0,0 +1,348 @@ +package code.name.monkey.retromusic.ui.fragments.player.color; + +import android.animation.ObjectAnimator; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatSeekBar; +import android.support.v7.widget.AppCompatTextView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.VolumeFragment; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +public class ColorPlaybackControlsFragment extends AbsPlayerControlsFragment { + + @BindView(R.id.player_play_pause_button) + ImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.title) + AppCompatTextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.volume_fragment_container) + View volumeContainer; + + private Unbinder unbinder; + private PlayPauseDrawable playerFabPlayPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + private VolumeFragment volumeFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_color_player_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + unbinder = ButterKnife.bind(this, view); + setUpMusicControllers(); + + if (PreferenceUtil.getInstance(getContext()).getVolumeToggle()) { + volumeContainer.setVisibility(View.VISIBLE); + } else { + volumeContainer.setVisibility(View.GONE); + } + + volumeFragment = (VolumeFragment) getChildFragmentManager().findFragmentById(R.id.volume_fragment); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + + public void setDark(int textColor, int background) { + setDark(textColor); + TintHelper.setTintAuto(playPauseFab, background, false); + TintHelper.setTintAuto(playPauseFab, textColor, true); + } + + @Override + public void setDark(int color) { + lastPlaybackControlsColor = color; + lastDisabledPlaybackControlsColor = ColorUtil.withAlpha(color, 0.5f); + + title.setTextColor(lastPlaybackControlsColor); + text.setTextColor(lastDisabledPlaybackControlsColor); + + TintHelper.setTintAuto(progressSlider, lastPlaybackControlsColor, false); + + volumeFragment.setTintable(lastPlaybackControlsColor); + + songCurrentProgress.setTextColor(lastDisabledPlaybackControlsColor); + songTotalTime.setTextColor(lastDisabledPlaybackControlsColor); + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + } + + + private void setUpPlayPauseFab() { + playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + + playPauseFab.setImageDrawable(playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called + playPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(Color.BLACK)), PorterDuff.Mode.SRC_IN); + //playPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseFab.post(() -> { + if (playPauseFab != null) { + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playerFabPlayPauseDrawable.setPause(animate); + } else { + playerFabPlayPauseDrawable.setPlay(animate); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + + @Override + protected void show() { + playPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + @Override + protected void hide() { + if (playPauseFab != null) { + playPauseFab.setScaleX(0f); + playPauseFab.setScaleY(0f); + playPauseFab.setRotation(0f); + } + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + public void showBouceAnimation() { + playPauseFab.clearAnimation(); + + playPauseFab.setScaleX(0.9f); + playPauseFab.setScaleY(0.9f); + playPauseFab.setVisibility(View.VISIBLE); + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + + playPauseFab.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseFab.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } + + public void hideVolumeIfAvailable() { + volumeContainer.setVisibility(PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + } + + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlaybackControlsFragment.java new file mode 100644 index 00000000..4c7b20fb --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlaybackControlsFragment.java @@ -0,0 +1,300 @@ +package code.name.monkey.retromusic.ui.fragments.player.flat; + +import android.animation.ObjectAnimator; +import android.graphics.PorterDuff; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsMusicServiceFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +public class FlatPlaybackControlsFragment extends AbsMusicServiceFragment implements Callback { + + @BindView(R.id.text) + TextView mText; + @BindView(R.id.title) + TextView mTitle; + @BindView(R.id.playback_controls) + ViewGroup viewGroup; + @BindView(R.id.player_song_total_time) + TextView mSongTotalTime; + @BindView(R.id.player_song_current_progress) + TextView mPlayerSongCurrentProgress; + @BindView(R.id.player_repeat_button) + ImageButton mPlayerRepeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton mPlayerShuffleButton; + @BindView(R.id.player_play_pause_button) + ImageView mPlayerPlayPauseFab; + Unbinder unbinder; + @BindView(R.id.player_progress_slider) + SeekBar progressSlider; + @BindView(R.id.volume_fragment_container) + View mVolumeContainer; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + private PlayPauseDrawable playerFabPlayPauseDrawable; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_flat_player_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpMusicControllers(); + + mVolumeContainer.setVisibility(PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + mPlayerSongCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + mSongTotalTime.setText(MusicUtil.getReadableDurationString(total)); + } + + + public void show() { + mPlayerPlayPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + + public void hide() { + if (mPlayerPlayPauseFab != null) { + mPlayerPlayPauseFab.setScaleX(0f); + mPlayerPlayPauseFab.setScaleY(0f); + mPlayerPlayPauseFab.setRotation(0f); + } + } + + public void setDark(int dark) { + int color = ATHUtil.resolveColor(getActivity(), android.R.attr.colorBackground); + boolean isDark = ColorUtil.isColorLight(color); + if (isDark) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false); + } + int accentColor = ThemeStore.accentColor(getContext()); + boolean b = PreferenceUtil.getInstance(getContext()).getAdaptiveColor(); + updateTextColors(b ? dark : accentColor); + setProgressBarColor(b ? dark : accentColor); + + + updateRepeatState(); + updateShuffleState(); + } + + private void setProgressBarColor(int dark) { + TintHelper.setTintAuto(progressSlider, dark, false); + //LayerDrawable ld = (LayerDrawable) progressSlider.getProgressDrawable(); + //ClipDrawable clipDrawable = (ClipDrawable) ld.findDrawableByLayerId(android.R.id.progress); + //clipDrawable.setColorFilter(dark, PorterDuff.Mode.SRC_IN); + } + + private void updateTextColors(int color) { + boolean isDark = ColorUtil.isColorLight(color); + int darkColor = ColorUtil.darkenColor(color); + int colorPrimary = MaterialValueHelper.getPrimaryTextColor(getContext(), isDark); + int colorSecondary = MaterialValueHelper.getSecondaryTextColor(getContext(), ColorUtil.isColorLight(darkColor)); + + TintHelper.setTintAuto(mPlayerPlayPauseFab, colorPrimary, false); + TintHelper.setTintAuto(mPlayerPlayPauseFab, color, true); + + mTitle.setBackgroundColor(color); + mTitle.setTextColor(colorPrimary); + mText.setBackgroundColor(darkColor); + mText.setTextColor(colorSecondary); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playerFabPlayPauseDrawable.setPause(animate); + } else { + playerFabPlayPauseDrawable.setPlay(animate); + } + } + + private void setUpPlayPauseFab() { + playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + + mPlayerPlayPauseFab.setImageDrawable( + playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called + //playPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(fabColor)), PorterDuff.Mode.SRC_IN); + mPlayerPlayPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + mPlayerPlayPauseFab.post(() -> { + if (mPlayerPlayPauseFab != null) { + mPlayerPlayPauseFab.setPivotX(mPlayerPlayPauseFab.getWidth() / 2); + mPlayerPlayPauseFab.setPivotY(mPlayerPlayPauseFab.getHeight() / 2); + } + }); + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void updateSong() { + //TransitionManager.beginDelayedTransition(viewGroup, new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN)); + Song song = MusicPlayerRemote.getCurrentSong(); + mTitle.setText(song.title); + mText.setText(song.artistName); + + } + + private void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), + MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + private void setUpRepeatButton() { + mPlayerRepeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + private void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + mPlayerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + mPlayerRepeatButton + .setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + mPlayerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + mPlayerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + mPlayerRepeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + mPlayerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpShuffleButton() { + mPlayerShuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + private void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + mPlayerShuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + mPlayerShuffleButton + .setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlayerFragment.java new file mode 100644 index 00000000..256e3f38 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/flat/FlatPlayerFragment.java @@ -0,0 +1,174 @@ +package code.name.monkey.retromusic.ui.fragments.player.flat; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.graphics.drawable.GradientDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.ViewUtil; +import code.name.monkey.retromusic.views.DrawableGradient; + +public class FlatPlayerFragment extends AbsPlayerFragment implements + PlayerAlbumCoverFragment.Callbacks { + + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.gradient_background) + View colorBackground; + @BindView(R.id.toolbar_container) + FrameLayout toolbarContainer; + @BindView(R.id.status_bar) + View statusBar; + + private Unbinder unbinder; + private ValueAnimator valueAnimator; + private FlatPlaybackControlsFragment flatPlaybackControlsFragment; + private int lastColor; + + private void setUpSubFragments() { + flatPlaybackControlsFragment = (FlatPlaybackControlsFragment) + getChildFragmentManager().findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = (PlayerAlbumCoverFragment) + getChildFragmentManager().findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, ATHUtil.resolveColor(getContext(), + R.attr.iconColor), getActivity()); + } + + private void colorize(int i) { + if (valueAnimator != null) { + valueAnimator.cancel(); + } + + valueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), android.R.color.transparent, i); + valueAnimator.addUpdateListener(animation -> { + GradientDrawable drawable = new DrawableGradient(GradientDrawable.Orientation.TOP_BOTTOM, + new int[]{(int) animation.getAnimatedValue(), android.R.color.transparent}, 0); + if (colorBackground != null) { + colorBackground.setBackground(drawable); + } + }); + valueAnimator.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME).start(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_flat_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpPlayerToolbar(); + setUpSubFragments(); + + } + + @Override + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + flatPlaybackControlsFragment.show(); + } + + @Override + public void onHide() { + flatPlaybackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + boolean isLight = ColorUtil.isColorLight(lastColor); + return PreferenceUtil.getInstance(getContext()).getAdaptiveColor() ? + MaterialValueHelper.getPrimaryTextColor(getContext(), isLight) : + ATHUtil.resolveColor(getContext(), R.attr.iconColor); + } + + @Override + public void onColorChanged(int color) { + lastColor = color; + flatPlaybackControlsFragment.setDark(color); + getCallbacks().onPaletteColorChanged(); + + boolean isLight = ColorUtil.isColorLight(color); + + //TransitionManager.beginDelayedTransition(mToolbar); + int iconColor = PreferenceUtil.getInstance(getContext()).getAdaptiveColor() ? + MaterialValueHelper.getPrimaryTextColor(getContext(), isLight) : + ATHUtil.resolveColor(getContext(), R.attr.iconColor); + ToolbarContentTintHelper.colorizeToolbar(toolbar, iconColor, getActivity()); + if (PreferenceUtil.getInstance(getContext()).getAdaptiveColor()) { + colorize(color); + } + } + + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/full/FullPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/full/FullPlaybackControlsFragment.java new file mode 100644 index 00000000..6eb93e4a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/full/FullPlaybackControlsFragment.java @@ -0,0 +1,301 @@ +package code.name.monkey.retromusic.ui.fragments.player.full; + +import android.animation.ObjectAnimator; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.VolumeFragment; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * Created by hemanths on 20/09/17. + */ + +public class FullPlaybackControlsFragment extends AbsPlayerControlsFragment { + @BindView(R.id.player_song_current_progress) + TextView mPlayerSongCurrentProgress; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_progress_slider) + SeekBar progressSlider; + @BindView(R.id.player_prev_button) + ImageButton playerPrevButton; + @BindView(R.id.player_next_button) + ImageButton playerNextButton; + @BindView(R.id.player_repeat_button) + ImageButton playerRepeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton playerShuffleButton; + @BindView(R.id.player_play_pause_button) + ImageButton playerPlayPauseFab; + Unbinder unbinder; + @BindView(R.id.title) + TextView mTitle; + @BindView(R.id.text) + TextView mText; + @BindView(R.id.volume_fragment_container) + View mVolumeContainer; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_full_player_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setUpMusicControllers(); + + mVolumeContainer.setVisibility(PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + VolumeFragment volumeFragment = (VolumeFragment) getChildFragmentManager().findFragmentById(R.id.volume_fragment); + volumeFragment.tintWhiteColor(); + + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + mPlayerSongCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + } + + + public void show() { + playerPlayPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + + public void hide() { + if (playerPlayPauseFab != null) { + playerPlayPauseFab.setScaleX(0f); + playerPlayPauseFab.setScaleY(0f); + playerPlayPauseFab.setRotation(0f); + } + } + + public void setDark(int dark) { + lastPlaybackControlsColor = Color.WHITE; + lastDisabledPlaybackControlsColor = ContextCompat.getColor(getContext(), R.color.md_grey_500); + + if (PreferenceUtil.getInstance(getContext()).getAdaptiveColor()) { + setProgressBarColor(dark); + } else { + int accentColor = ThemeStore.accentColor(getContext()); + setProgressBarColor(accentColor); + } + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + updateProgressTextColor(); + } + + private void setProgressBarColor(int dark) { + LayerDrawable ld = (LayerDrawable) progressSlider.getProgressDrawable(); + ClipDrawable clipDrawable = (ClipDrawable) ld.findDrawableByLayerId(android.R.id.progress); + clipDrawable.setColorFilter(dark, PorterDuff.Mode.SRC_IN); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + mTitle.setText(song.title); + mText.setText(song.artistName); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + + if (MusicPlayerRemote.isPlaying()) { + playerPlayPauseFab.setImageResource(R.drawable.ic_pause_white_24dp); + } else { + playerPlayPauseFab.setImageResource(R.drawable.ic_play_arrow_white_24dp); + } + } + + private void setUpPlayPauseFab() { + + playerPlayPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playerPlayPauseFab.post(() -> { + if (playerPlayPauseFab != null) { + playerPlayPauseFab.setPivotX(playerPlayPauseFab.getWidth() / 2); + playerPlayPauseFab.setPivotY(playerPlayPauseFab.getHeight() / 2); + } + }); + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + playerNextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + playerPrevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updateProgressTextColor() { + int color = MaterialValueHelper.getSecondaryTextColor(getContext(), false); + //songTotalTime.setTextColor(color); + //songCurrentProgress.setTextColor(color); + } + + private void updatePrevNextColor() { + playerNextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + playerPrevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + private void setUpRepeatButton() { + playerRepeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + playerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + playerRepeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + playerRepeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + playerRepeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + playerRepeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpShuffleButton() { + playerShuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + public void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + playerShuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + playerShuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/full/FullPlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/full/FullPlayerFragment.java new file mode 100644 index 00000000..cb1e0d22 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/full/FullPlayerFragment.java @@ -0,0 +1,123 @@ +package code.name.monkey.retromusic.ui.fragments.player.full; + +import android.graphics.Color; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; + +public class FullPlayerFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks { + @BindView(R.id.player_toolbar) + Toolbar toolbar; + + Unbinder unbinder; + + private int lastColor; + private FullPlaybackControlsFragment fullPlaybackControlsFragment; + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_full, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + fullPlaybackControlsFragment = (FullPlaybackControlsFragment) + getChildFragmentManager().findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = (PlayerAlbumCoverFragment) + getChildFragmentManager().findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + playerAlbumCoverFragment.removeSlideEffect(); + } + + @Override + @ColorInt + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + + } + + @Override + public void onHide() { + + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return Color.WHITE; + } + + @Override + public void onColorChanged(int color) { + lastColor = color; + fullPlaybackControlsFragment.setDark(color); + getCallbacks().onPaletteColorChanged(); + ToolbarContentTintHelper.colorizeToolbar(toolbar, Color.WHITE, getActivity()); + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/hmm/HmmPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/hmm/HmmPlaybackControlsFragment.java new file mode 100644 index 00000000..3bb3cff3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/hmm/HmmPlaybackControlsFragment.java @@ -0,0 +1,144 @@ +package code.name.monkey.retromusic.ui.fragments.player.hmm; + +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; + +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; + +/** + * @author Hemanth S (h4h13). + */ + +public class HmmPlaybackControlsFragment extends AbsPlayerControlsFragment { + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + private Unbinder unbinder; + private int mLastPlaybackControlsColor; + private int mLastDisabledPlaybackControlsColor; + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_hmm_controls_fragment, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpMusicControllers(); + } + + private void setUpMusicControllers() { + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + @Override + protected void show() { + + } + + @Override + protected void hide() { + + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(mLastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(mLastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(mLastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(mLastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(mLastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + @Override + protected void setUpProgressSlider() { + + } + + @Override + public void setDark(int dark) { + if (ColorUtil.isColorLight(dark)) { + mLastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + mLastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true); + } else { + mLastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + mLastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false); + } + + updateRepeatState(); + updateShuffleState(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/hmm/HmmPlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/hmm/HmmPlayerFragment.java new file mode 100644 index 00000000..bdd890ae --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/hmm/HmmPlayerFragment.java @@ -0,0 +1,220 @@ +package code.name.monkey.retromusic.ui.fragments.player.hmm; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.LinearInterpolator; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.afollestad.materialdialogs.internal.MDTintHelper; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.MiniPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class HmmPlayerFragment extends AbsPlayerFragment implements + MusicProgressViewUpdateHelper.Callback, PlayerAlbumCoverFragment.Callbacks { + + @BindView(R.id.title) + TextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.player_song_total_time) + TextView totalTime; + @BindView(R.id.progress_bar) + ProgressBar progressBar; + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.status_bar) + View statusBar; + + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + private Unbinder unBinder; + private int lastColor; + private HmmPlaybackControlsFragment hmmPlaybackControlsFragment; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(String.format("%s \nby -%s", song.albumName, song.artistName)); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unBinder.unbind(); + + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_hmm_player, container, false); + unBinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + progressBar.setOnClickListener(new PlayPauseButtonOnClickHandler()); + progressBar.setOnTouchListener(new MiniPlayerFragment.FlingPlayBackController(getActivity())); + + setUpPlayerToolbar(); + setUpSubFragments(); + } + + private void setUpSubFragments() { + hmmPlaybackControlsFragment = (HmmPlaybackControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + PlayerAlbumCoverFragment playerAlbumCoverFragment = (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + + } + + @Override + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + hmmPlaybackControlsFragment.show(); + } + + @Override + public void onHide() { + hmmPlaybackControlsFragment.hide(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return MaterialValueHelper + .getSecondaryTextColor(getContext(), ColorUtil.isColorLight(lastColor)); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressBar.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressBar, "progress", progress); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playSequentially(animator); + + animatorSet.setDuration(1500); + animatorSet.setInterpolator(new LinearInterpolator()); + animatorSet.start(); + + totalTime.setText(String.format("%s/%s", MusicUtil.getReadableDurationString(total), + MusicUtil.getReadableDurationString(progress))); + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + } + + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onColorChanged(int color) { + lastColor = PreferenceUtil.getInstance(getContext()).getAdaptiveColor() ? color : + ThemeStore.accentColor(getContext()); + getCallbacks().onPaletteColorChanged(); + hmmPlaybackControlsFragment.setDark(lastColor); + setProgressBarColor(lastColor); + + int iconColor = MaterialValueHelper + .getSecondaryTextColor(getContext(), ColorUtil.isColorLight(lastColor)); + ToolbarContentTintHelper.colorizeToolbar(toolbar, iconColor, getActivity()); + } + + private void setProgressBarColor(int color) { + MDTintHelper.setTint(progressBar, color); + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/lockscreen/LockScreenPlayerControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/lockscreen/LockScreenPlayerControlsFragment.java new file mode 100644 index 00000000..8acfdd47 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/lockscreen/LockScreenPlayerControlsFragment.java @@ -0,0 +1,294 @@ +package code.name.monkey.retromusic.ui.fragments.player.lockscreen; + +import android.animation.ObjectAnimator; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatImageButton; +import android.support.v7.widget.AppCompatSeekBar; +import android.support.v7.widget.AppCompatTextView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.VolumeFragment; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; + +/** + * @author Hemanth S (h4h13). + */ +public class LockScreenPlayerControlsFragment extends AbsPlayerControlsFragment { + @BindView(R.id.player_play_pause_button) + AppCompatImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.title) + AppCompatTextView title; + @BindView(R.id.text) + AppCompatTextView text; + + private Unbinder unbinder; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + private int lastPlaybackControlsColor; + + public LockScreenPlayerControlsFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_lock_screen_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpMusicControllers(); + + VolumeFragment volumeFragment = (VolumeFragment) getChildFragmentManager().findFragmentById(R.id.volume_fragment); + + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + + title.setText(song.title); + text.setText(String.format("%s - %s", song.artistName, song.albumName)); + + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void setDark(int dark) { + setProgressBarColor(progressSlider, dark); + + if (ColorUtil.isColorLight(ATHUtil.resolveColor(getContext(), android.R.attr.windowBackground))) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + + } + + updatePrevNextColor(); + + boolean isDark = ColorUtil.isColorLight(dark); + text.setTextColor(dark); + TintHelper.setTintAuto(playPauseFab, MaterialValueHelper.getPrimaryTextColor(getContext(), isDark), false); + TintHelper.setTintAuto(playPauseFab, dark, true); + } + + public void setProgressBarColor(SeekBar progressBar, int newColor) { + TintHelper.setTintAuto(progressBar, newColor, false); + //LayerDrawable ld = (LayerDrawable) progressBar.getProgressDrawable(); + //ClipDrawable clipDrawable = (ClipDrawable) ld.findDrawableByLayerId(android.R.id.progress); + //clipDrawable.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpPlayPauseFab() { + playPauseFab.post(() -> { + if (playPauseFab != null) { + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + } + }); + } + + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + @Override + protected void show() { + playPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + @Override + protected void hide() { + if (playPauseFab != null) { + playPauseFab.setScaleX(0f); + playPauseFab.setScaleY(0f); + playPauseFab.setRotation(0f); + } + } + + @Override + protected void updateShuffleState() { + //TODO(Nothing to Implement) + } + + @Override + protected void updateRepeatState() { + //TODO(Nothing to Implement) + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), + MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + public void showBouceAnimation() { + playPauseFab.clearAnimation(); + + playPauseFab.setScaleX(0.9f); + playPauseFab.setScaleY(0.9f); + playPauseFab.setVisibility(View.VISIBLE); + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + + playPauseFab.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseFab.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playPauseFab.setImageResource(R.drawable.ic_pause_white_24dp); + } else { + playPauseFab.setImageResource(R.drawable.ic_play_arrow_white_24dp); + } + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/material/MaterialControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/material/MaterialControlsFragment.java new file mode 100644 index 00000000..a2425edb --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/material/MaterialControlsFragment.java @@ -0,0 +1,273 @@ +package code.name.monkey.retromusic.ui.fragments.player.material; + +import android.animation.ObjectAnimator; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatSeekBar; +import android.support.v7.widget.AppCompatTextView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * @author Hemanth S (h4h13). + */ +public class MaterialControlsFragment extends AbsPlayerControlsFragment { + + @BindView(R.id.player_play_pause_button) + ImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.title) + AppCompatTextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.volume_fragment_container) + View mVolumeContainer; + private Unbinder unbinder; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_material_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + unbinder = ButterKnife.bind(this, view); + setUpMusicControllers(); + + if (PreferenceUtil.getInstance(getContext()).getVolumeToggle()) { + mVolumeContainer.setVisibility(View.VISIBLE); + } else { + mVolumeContainer.setVisibility(View.GONE); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void setDark(int dark) { + int color = ATHUtil.resolveColor(getActivity(), android.R.attr.colorBackground); + if (ColorUtil.isColorLight(color)) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false); + } + + updatePlayPauseColor(); + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + } + + private void setUpPlayPauseFab() { + playPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + } + + protected void updatePlayPauseDrawableState() { + if (MusicPlayerRemote.isPlaying()) { + playPauseFab.setImageResource(R.drawable.ic_pause_white_big); + } else { + playPauseFab.setImageResource(R.drawable.ic_play_arrow_white_big); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void updatePlayPauseColor() { + playPauseFab.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + @Override + protected void show() { + + } + + @Override + protected void hide() { + + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), + MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/material/MaterialFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/material/MaterialFragment.java new file mode 100644 index 00000000..a78c799b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/material/MaterialFragment.java @@ -0,0 +1,153 @@ +package code.name.monkey.retromusic.ui.fragments.player.material; + +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.ui.fragments.player.normal.PlayerFragment; + +/** + * @author Hemanth S (h4h13). + */ +public class MaterialFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks { + + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.status_bar) + View statusBar; + + private int lastColor; + private MaterialControlsFragment playbackControlsFragment; + private Unbinder unbinder; + + public static PlayerFragment newInstance() { + Bundle args = new Bundle(); + PlayerFragment fragment = new PlayerFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + @ColorInt + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + playbackControlsFragment.show(); + } + + @Override + public void onHide() { + playbackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return ATHUtil.resolveColor(getContext(), R.attr.iconColor); + } + + @Override + public void onColorChanged(int color) { + playbackControlsFragment.setDark(color); + lastColor = color; + getCallbacks().onPaletteColorChanged(); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_material, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + playbackControlsFragment = (MaterialControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = + (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + } + + @Override + public void onServiceConnected() { + updateIsFavorite(); + } + + @Override + public void onPlayingMetaChanged() { + updateIsFavorite(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerFragment.java new file mode 100644 index 00000000..9914b72b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerFragment.java @@ -0,0 +1,181 @@ +package code.name.monkey.retromusic.ui.fragments.player.normal; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.graphics.drawable.GradientDrawable; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.util.ViewUtil; +import code.name.monkey.retromusic.views.DrawableGradient; + + +public class PlayerFragment extends AbsPlayerFragment implements PlayerAlbumCoverFragment.Callbacks { + + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.gradient_background) + View colorBackground; + @BindView(R.id.status_bar) + View statusBar; + + private int lastColor; + private PlayerPlaybackControlsFragment playbackControlsFragment; + private Unbinder unbinder; + private ValueAnimator valueAnimator; + + public static PlayerFragment newInstance() { + Bundle args = new Bundle(); + PlayerFragment fragment = new PlayerFragment(); + fragment.setArguments(args); + return fragment; + } + + + private void colorize(int i) { + if (valueAnimator != null) { + valueAnimator.cancel(); + } + + valueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), android.R.color.transparent, i); + valueAnimator.addUpdateListener(animation -> { + GradientDrawable drawable = new DrawableGradient(GradientDrawable.Orientation.TOP_BOTTOM, + new int[]{(int) animation.getAnimatedValue(), android.R.color.transparent}, 0); + if (colorBackground != null) { + colorBackground.setBackground(drawable); + } + }); + valueAnimator.setDuration(ViewUtil.RETRO_MUSIC_ANIM_TIME).start(); + } + + @Override + @ColorInt + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + playbackControlsFragment.show(); + } + + @Override + public void onHide() { + playbackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return ATHUtil.resolveColor(getContext(), R.attr.iconColor); + } + + @Override + public void onColorChanged(int color) { + playbackControlsFragment.setDark(color); + lastColor = color; + getCallbacks().onPaletteColorChanged(); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + + if (PreferenceUtil.getInstance(getContext()).getAdaptiveColor()) { + colorize(color); + } + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + playbackControlsFragment = (PlayerPlaybackControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + + PlayerAlbumCoverFragment playerAlbumCoverFragment = + (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + } + + @Override + public void onServiceConnected() { + updateIsFavorite(); + + } + + @Override + public void onPlayingMetaChanged() { + updateIsFavorite(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerPlaybackControlsFragment.java new file mode 100644 index 00000000..4f0303e8 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerPlaybackControlsFragment.java @@ -0,0 +1,345 @@ +package code.name.monkey.retromusic.ui.fragments.player.normal; + +import android.animation.ObjectAnimator; +import android.graphics.PorterDuff; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatSeekBar; +import android.support.v7.widget.AppCompatTextView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +public class PlayerPlaybackControlsFragment extends AbsPlayerControlsFragment { + + @BindView(R.id.player_play_pause_button) + ImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + AppCompatSeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.title) + AppCompatTextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.volume_fragment_container) + View mVolumeContainer; + private Unbinder unbinder; + private PlayPauseDrawable playerFabPlayPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_player_playback_controls, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + unbinder = ButterKnife.bind(this, view); + setUpMusicControllers(); + + if (PreferenceUtil.getInstance(getContext()).getVolumeToggle()) { + mVolumeContainer.setVisibility(View.VISIBLE); + } else { + mVolumeContainer.setVisibility(View.GONE); + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void setDark(int dark) { + int color = ATHUtil.resolveColor(getActivity(), android.R.attr.colorBackground); + if (ColorUtil.isColorLight(color)) { + lastPlaybackControlsColor = MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + lastDisabledPlaybackControlsColor = MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false); + } + + if (PreferenceUtil.getInstance(getContext()).getAdaptiveColor()) { + setFabColor(dark); + } else { + setFabColor(ThemeStore.accentColor(getContext())); + } + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + } + + private void setFabColor(int i) { + TintHelper.setTintAuto(playPauseFab, MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(i)), false); + TintHelper.setTintAuto(playPauseFab, i, true); + setProgressBarColor(i); + } + + public void setProgressBarColor(int newColor) { + LayerDrawable ld = (LayerDrawable) progressSlider.getProgressDrawable(); + ClipDrawable clipDrawable = (ClipDrawable) ld.findDrawableByLayerId(android.R.id.progress); + clipDrawable.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpPlayPauseFab() { + playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + + playPauseFab.setImageDrawable( + playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called + //playPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(fabColor)), PorterDuff.Mode.SRC_IN); + //playPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseFab.post(() -> { + if (playPauseFab != null) { + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playerFabPlayPauseDrawable.setPause(animate); + } else { + playerFabPlayPauseDrawable.setPlay(animate); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + @Override + protected void show() { + playPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + @Override + protected void hide() { + if (playPauseFab != null) { + playPauseFab.setScaleX(0f); + playPauseFab.setScaleY(0f); + playPauseFab.setRotation(0f); + } + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), + MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + public void showBouceAnimation() { + playPauseFab.clearAnimation(); + + playPauseFab.setScaleX(0.9f); + playPauseFab.setScaleY(0.9f); + playPauseFab.setVisibility(View.VISIBLE); + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + + playPauseFab.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseFab.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/plain/PlainPlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/plain/PlainPlaybackControlsFragment.java new file mode 100644 index 00000000..c32d8397 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/plain/PlainPlaybackControlsFragment.java @@ -0,0 +1,324 @@ +package code.name.monkey.retromusic.ui.fragments.player.plain; + +import android.animation.ObjectAnimator; +import android.graphics.PorterDuff; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +/** + * @author Hemanth S (h4h13). + */ + +public class PlainPlaybackControlsFragment extends AbsPlayerControlsFragment { + @BindView(R.id.player_play_pause_button) + ImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_progress_slider) + SeekBar progressSlider; + @BindView(R.id.player_song_total_time) + TextView songTotalTime; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.volume_fragment_container) + View volumeContainer; + private Unbinder unbinder; + private PlayPauseDrawable playerFabPlayPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_plain_controls_fragment, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpMusicControllers(); + if (PreferenceUtil.getInstance(getContext()).getVolumeToggle()) { + volumeContainer.setVisibility(View.VISIBLE); + } else { + volumeContainer.setVisibility(View.GONE); + } + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + + @Override + protected void show() { + playPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + @Override + protected void hide() { + if (playPauseFab != null) { + playPauseFab.setScaleX(0f); + playPauseFab.setScaleY(0f); + playPauseFab.setRotation(0f); + } + } + + @Override + protected void setUpProgressSlider() { + progressSlider.setOnSeekBarChangeListener(new SimpleOnSeekbarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + MusicPlayerRemote.seekTo(progress); + onUpdateProgressViews(MusicPlayerRemote.getSongProgressMillis(), MusicPlayerRemote.getSongDurationMillis()); + } + } + }); + } + + public void showBouceAnimation() { + playPauseFab.clearAnimation(); + + playPauseFab.setScaleX(0.9f); + playPauseFab.setScaleY(0.9f); + playPauseFab.setVisibility(View.VISIBLE); + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + + playPauseFab.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseFab.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + progressSlider.setMax(total); + + ObjectAnimator animator = ObjectAnimator.ofInt(progressSlider, "progress", progress); + animator.setDuration(1500); + animator.setInterpolator(new LinearInterpolator()); + animator.start(); + + songTotalTime.setText(MusicUtil.getReadableDurationString(total)); + songCurrentProgress.setText(MusicUtil.getReadableDurationString(progress)); + } + + @Override + public void setDark(int dark) { + int color = ATHUtil.resolveColor(getActivity(), android.R.attr.colorBackground); + if (ColorUtil.isColorLight(color)) { + lastPlaybackControlsColor = + MaterialValueHelper.getSecondaryTextColor(getActivity(), true); + lastDisabledPlaybackControlsColor = + MaterialValueHelper.getSecondaryDisabledTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = + MaterialValueHelper.getPrimaryTextColor(getActivity(), false); + lastDisabledPlaybackControlsColor = + MaterialValueHelper.getPrimaryDisabledTextColor(getActivity(), false); + } + + if (PreferenceUtil.getInstance(getContext()).getAdaptiveColor()) { + TintHelper.setTintAuto(playPauseFab, MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(dark)), false); + TintHelper.setTintAuto(playPauseFab, dark, true); + setProgressBarColor(progressSlider, dark); + } + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + } + + public void setProgressBarColor(SeekBar progressBar, int newColor) { + LayerDrawable ld = (LayerDrawable) progressBar.getProgressDrawable(); + ClipDrawable clipDrawable = (ClipDrawable) ld.findDrawableByLayerId(android.R.id.progress); + clipDrawable.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpPlayPauseFab() { + playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + + playPauseFab.setImageDrawable(playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called + //playPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(fabColor)), PorterDuff.Mode.SRC_IN); + //playPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseFab.post(() -> { + if (playPauseFab != null) { + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playerFabPlayPauseDrawable.setPause(animate); + } else { + playerFabPlayPauseDrawable.setPlay(animate); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/plain/PlainPlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/plain/PlainPlayerFragment.java new file mode 100644 index 00000000..f448fee5 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/plain/PlainPlayerFragment.java @@ -0,0 +1,158 @@ +package code.name.monkey.retromusic.ui.fragments.player.plain; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; + +/** + * @author Hemanth S (h4h13). + */ + +public class PlainPlayerFragment extends AbsPlayerFragment implements + PlayerAlbumCoverFragment.Callbacks { + + @BindView(R.id.title) + TextView title; + @BindView(R.id.text) + TextView text; + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.toolbar_container) + FrameLayout toolbarContainer; + @BindView(R.id.status_bar) + View statusBar; + + private Unbinder unbinder; + private PlainPlaybackControlsFragment plainPlaybackControlsFragment; + private int mLastColor; + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + } + + @Override + public void onServiceConnected() { + super.onServiceConnected(); + updateSong(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_plain_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), + getActivity()); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + setUpSubFragments(); + setUpPlayerToolbar(); + title.setSelected(true); + } + + private void setUpSubFragments() { + plainPlaybackControlsFragment = (PlainPlaybackControlsFragment) getChildFragmentManager() + .findFragmentById(R.id.playback_controls_fragment); + PlayerAlbumCoverFragment playerAlbumCoverFragment = (PlayerAlbumCoverFragment) getChildFragmentManager() + .findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + } + + @Override + public int getPaletteColor() { + return mLastColor; + } + + @Override + public void onShow() { + plainPlaybackControlsFragment.show(); + } + + @Override + public void onHide() { + plainPlaybackControlsFragment.hide(); + onBackPressed(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return ATHUtil.resolveColor(getContext(), R.attr.iconColor); + } + + @Override + public void onColorChanged(int color) { + plainPlaybackControlsFragment.setDark(color); + mLastColor = color; + getCallbacks().onPaletteColorChanged(); + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/simple/SimplePlaybackControlsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/simple/SimplePlaybackControlsFragment.java new file mode 100644 index 00000000..1631a757 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/simple/SimplePlaybackControlsFragment.java @@ -0,0 +1,319 @@ +package code.name.monkey.retromusic.ui.fragments.player.simple; + +import android.graphics.PorterDuff; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerControlsFragment; +import code.name.monkey.retromusic.util.MusicUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; +import code.name.monkey.retromusic.views.PlayPauseDrawable; + +/** + * @author Hemanth S (h4h13). + */ + +public class SimplePlaybackControlsFragment extends AbsPlayerControlsFragment { + @BindView(R.id.player_play_pause_button) + ImageButton playPauseFab; + @BindView(R.id.player_prev_button) + ImageButton prevButton; + @BindView(R.id.player_next_button) + ImageButton nextButton; + @BindView(R.id.player_repeat_button) + ImageButton repeatButton; + @BindView(R.id.player_shuffle_button) + ImageButton shuffleButton; + @BindView(R.id.player_song_current_progress) + TextView songCurrentProgress; + @BindView(R.id.volume_fragment_container) + View volumeContainer; + @BindView(R.id.title) + TextView title; + @BindView(R.id.text) + TextView text; + private Unbinder unbinder; + private PlayPauseDrawable playerFabPlayPauseDrawable; + private int lastPlaybackControlsColor; + private int lastDisabledPlaybackControlsColor; + private MusicProgressViewUpdateHelper progressViewUpdateHelper; + + + @Override + public void onPlayStateChanged() { + updatePlayPauseDrawableState(true); + } + + @Override + public void onRepeatModeChanged() { + updateRepeatState(); + } + + @Override + public void onShuffleModeChanged() { + updateShuffleState(); + } + + @Override + public void onServiceConnected() { + updatePlayPauseDrawableState(false); + updateRepeatState(); + updateShuffleState(); + updateSong(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + progressViewUpdateHelper = new MusicProgressViewUpdateHelper(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_simple_controls_fragment, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onResume() { + super.onResume(); + progressViewUpdateHelper.start(); + } + + @Override + public void onPause() { + super.onPause(); + progressViewUpdateHelper.stop(); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setUpMusicControllers(); + volumeContainer.setVisibility(PreferenceUtil.getInstance(getContext()).getVolumeToggle() ? View.VISIBLE : View.GONE); + } + + private void setUpMusicControllers() { + setUpPlayPauseFab(); + setUpPrevNext(); + setUpRepeatButton(); + setUpShuffleButton(); + setUpProgressSlider(); + } + + private void setUpPrevNext() { + updatePrevNextColor(); + nextButton.setOnClickListener(v -> MusicPlayerRemote.playNextSong()); + prevButton.setOnClickListener(v -> MusicPlayerRemote.back()); + } + + private void updatePrevNextColor() { + nextButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + prevButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpShuffleButton() { + shuffleButton.setOnClickListener(v -> MusicPlayerRemote.toggleShuffleMode()); + } + + @Override + protected void updateShuffleState() { + switch (MusicPlayerRemote.getShuffleMode()) { + case MusicService.SHUFFLE_MODE_SHUFFLE: + shuffleButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + default: + shuffleButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void setUpRepeatButton() { + repeatButton.setOnClickListener(v -> MusicPlayerRemote.cycleRepeatMode()); + } + + @Override + protected void updateRepeatState() { + switch (MusicPlayerRemote.getRepeatMode()) { + case MusicService.REPEAT_MODE_NONE: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastDisabledPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_ALL: + repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + case MusicService.REPEAT_MODE_THIS: + repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp); + repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN); + break; + } + } + + private void updateSong() { + Song song = MusicPlayerRemote.getCurrentSong(); + title.setText(song.title); + text.setText(song.artistName); + } + + @Override + public void onPlayingMetaChanged() { + super.onPlayingMetaChanged(); + updateSong(); + + } + + @Override + protected void setUpProgressSlider() { + + } + + @Override + protected void show() { + playPauseFab.animate() + .scaleX(1f) + .scaleY(1f) + .rotation(360f) + .setInterpolator(new DecelerateInterpolator()) + .start(); + } + + @Override + protected void hide() { + if (playPauseFab != null) { + playPauseFab.setScaleX(0f); + playPauseFab.setScaleY(0f); + playPauseFab.setRotation(0f); + } + } + + + public void showBouceAnimation() { + playPauseFab.clearAnimation(); + + playPauseFab.setScaleX(0.9f); + playPauseFab.setScaleY(0.9f); + playPauseFab.setVisibility(View.VISIBLE); + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + + playPauseFab.animate() + .setDuration(200) + .setInterpolator(new DecelerateInterpolator()) + .scaleX(1.1f) + .scaleY(1.1f) + .withEndAction(() -> playPauseFab.animate() + .setDuration(200) + .setInterpolator(new AccelerateInterpolator()) + .scaleX(1f) + .scaleY(1f) + .alpha(1f) + .start()) + .start(); + } + + @OnClick(R.id.player_play_pause_button) + void showAnimation() { + if (MusicPlayerRemote.isPlaying()) { + MusicPlayerRemote.pauseSong(); + } else { + MusicPlayerRemote.resumePlaying(); + } + showBouceAnimation(); + } + + @Override + public void onUpdateProgressViews(int progress, int total) { + songCurrentProgress.setText(String.format("%s / %s", MusicUtil.getReadableDurationString(progress), + MusicUtil.getReadableDurationString(total))); + } + + @Override + public void setDark(int dark) { + int color = ATHUtil.resolveColor(getActivity(), android.R.attr.colorBackground); + if (ColorUtil.isColorLight(color)) { + lastPlaybackControlsColor = MaterialValueHelper + .getSecondaryTextColor(getActivity(), true); + lastDisabledPlaybackControlsColor = MaterialValueHelper + .getSecondaryDisabledTextColor(getActivity(), true); + } else { + lastPlaybackControlsColor = MaterialValueHelper + .getPrimaryTextColor(getActivity(), false); + lastDisabledPlaybackControlsColor = MaterialValueHelper + .getPrimaryDisabledTextColor(getActivity(), false); + } + + if (PreferenceUtil.getInstance(getContext()).getAdaptiveColor()) { + TintHelper.setTintAuto(playPauseFab, MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(dark)), false); + TintHelper.setTintAuto(playPauseFab, dark, true); + text.setTextColor(dark); + } else { + text.setTextColor(ThemeStore.accentColor(getContext())); + } + + updateRepeatState(); + updateShuffleState(); + updatePrevNextColor(); + } + + public void setProgressBarColor(SeekBar progressBar, int newColor) { + LayerDrawable ld = (LayerDrawable) progressBar.getProgressDrawable(); + ClipDrawable clipDrawable = (ClipDrawable) ld.findDrawableByLayerId(android.R.id.progress); + clipDrawable.setColorFilter(newColor, PorterDuff.Mode.SRC_IN); + } + + private void setUpPlayPauseFab() { + playerFabPlayPauseDrawable = new PlayPauseDrawable(getActivity()); + + playPauseFab.setImageDrawable(playerFabPlayPauseDrawable); // Note: set the drawable AFTER TintHelper.setTintAuto() was called + //playPauseFab.setColorFilter(MaterialValueHelper.getPrimaryTextColor(getContext(), ColorUtil.isColorLight(fabColor)), PorterDuff.Mode.SRC_IN); + //playPauseFab.setOnClickListener(new PlayPauseButtonOnClickHandler()); + playPauseFab.post(() -> { + if (playPauseFab != null) { + playPauseFab.setPivotX(playPauseFab.getWidth() / 2); + playPauseFab.setPivotY(playPauseFab.getHeight() / 2); + } + }); + } + + protected void updatePlayPauseDrawableState(boolean animate) { + if (MusicPlayerRemote.isPlaying()) { + playerFabPlayPauseDrawable.setPause(animate); + } else { + playerFabPlayPauseDrawable.setPlay(animate); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/simple/SimplePlayerFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/simple/SimplePlayerFragment.java new file mode 100644 index 00000000..0ac6ecff --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/simple/SimplePlayerFragment.java @@ -0,0 +1,132 @@ +package code.name.monkey.retromusic.ui.fragments.player.simple; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.util.ATHUtil; +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.ui.fragments.base.AbsPlayerFragment; +import code.name.monkey.retromusic.ui.fragments.player.PlayerAlbumCoverFragment; + +/** + * @author Hemanth S (h4h13). + */ + +public class SimplePlayerFragment extends AbsPlayerFragment implements + PlayerAlbumCoverFragment.Callbacks { + + @BindView(R.id.player_toolbar) + Toolbar toolbar; + @BindView(R.id.status_bar) + View statusBar; + private Unbinder unbinder; + private SimplePlaybackControlsFragment simplePlaybackControlsFragment; + private int lastColor; + + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_simple_player, container, false); + unbinder = ButterKnife.bind(this, view); + return view; + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + toggleStatusBar(statusBar); + setUpSubFragments(); + setUpPlayerToolbar(); + } + + private void setUpSubFragments() { + PlayerAlbumCoverFragment playerAlbumCoverFragment = (PlayerAlbumCoverFragment) + getChildFragmentManager().findFragmentById(R.id.player_album_cover_fragment); + playerAlbumCoverFragment.setCallbacks(this); + simplePlaybackControlsFragment = (SimplePlaybackControlsFragment) + getChildFragmentManager().findFragmentById(R.id.playback_controls_fragment); + + } + + @Override + public int getPaletteColor() { + return lastColor; + } + + @Override + public void onShow() { + simplePlaybackControlsFragment.show(); + } + + @Override + public void onHide() { + simplePlaybackControlsFragment.hide(); + } + + @Override + public boolean onBackPressed() { + return false; + } + + @Override + public Toolbar getToolbar() { + return toolbar; + } + + @Override + public int toolbarIconColor() { + return ATHUtil.resolveColor(getContext(), R.attr.iconColor); + } + + @Override + public void onColorChanged(int color) { + lastColor = color; + getCallbacks().onPaletteColorChanged(); + simplePlaybackControlsFragment.setDark(color); + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), getActivity()); + + } + + @Override + public void onFavoriteToggled() { + toggleFavorite(MusicPlayerRemote.getCurrentSong()); + } + + @Override + protected void toggleFavorite(Song song) { + super.toggleFavorite(song); + if (song.id == MusicPlayerRemote.getCurrentSong().id) { + updateIsFavorite(); + } + } + + private void setUpPlayerToolbar() { + toolbar.inflateMenu(R.menu.menu_player); + toolbar.setNavigationOnClickListener(v -> getActivity().onBackPressed()); + toolbar.setOnMenuItemClickListener(this); + + ToolbarContentTintHelper.colorizeToolbar(toolbar, + ATHUtil.resolveColor(getContext(), R.attr.iconColor), + getActivity()); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/AbsSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/AbsSettingsFragment.java new file mode 100644 index 00000000..a42528a1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/AbsSettingsFragment.java @@ -0,0 +1,89 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.Toast; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat; +import code.name.monkey.retromusic.preferences.BlacklistPreference; +import code.name.monkey.retromusic.preferences.BlacklistPreferenceDialog; +import code.name.monkey.retromusic.preferences.NowPlayingScreenPreference; +import code.name.monkey.retromusic.preferences.NowPlayingScreenPreferenceDialog; +import code.name.monkey.retromusic.ui.activities.SettingsActivity; +import code.name.monkey.retromusic.util.DensityUtil; +import code.name.monkey.retromusic.util.NavigationUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public abstract class AbsSettingsFragment extends ATEPreferenceFragmentCompat { + protected void showProToastAndNavigate(String message) { + Toast.makeText(getContext(), message + " is Pro version feature.", Toast.LENGTH_SHORT).show(); + //noinspection ConstantConditions + NavigationUtil.goToProVersion(getActivity()); + } + + protected void setSummary(@NonNull Preference preference) { + setSummary(preference, PreferenceManager + .getDefaultSharedPreferences(preference.getContext()) + .getString(preference.getKey(), "")); + } + + protected void setSummary(Preference preference, @NonNull Object value) { + String stringValue = value.toString(); + if (preference instanceof ListPreference) { + ListPreference listPreference = (ListPreference) preference; + int index = listPreference.findIndexOfValue(stringValue); + preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null); + } else { + preference.setSummary(stringValue); + } + } + + public abstract void invalidateSettings(); + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setDividerHeight(0); + setDivider(new ColorDrawable(Color.TRANSPARENT)); + + getListView().setPadding(DensityUtil.dip2px(getContext(), 0), 0, 0, 0); + getListView().addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if (getActivity() != null) { + ((SettingsActivity) getActivity()) + .addAppbarLayoutElevation(recyclerView.canScrollVertically(RecyclerView.NO_POSITION) ? 8f : 0f); + } + } + }); + //noinspection ConstantConditions + getListView().setBackgroundColor(ThemeStore.primaryColor(getContext())); + getListView().setOverScrollMode(View.OVER_SCROLL_NEVER); + invalidateSettings(); + } + + @Nullable + @Override + public DialogFragment onCreatePreferenceDialog(Preference preference) { + if (preference instanceof NowPlayingScreenPreference) { + return NowPlayingScreenPreferenceDialog.newInstance(); + } else if (preference instanceof BlacklistPreference) { + return BlacklistPreferenceDialog.newInstance(); + } + return super.onCreatePreferenceDialog(preference); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/AudioSettings.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/AudioSettings.java new file mode 100644 index 00000000..4135304f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/AudioSettings.java @@ -0,0 +1,49 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.media.audiofx.AudioEffect; +import android.os.Bundle; +import android.support.v7.preference.Preference; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.NavigationUtil; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class AudioSettings extends AbsSettingsFragment { + @Override + public void invalidateSettings() { + Preference findPreference = findPreference("equalizer"); + if (!hasEqualizer() && !PreferenceUtil.getInstance(getContext()).getSelectedEqualizer().equals("retro")) { + findPreference.setEnabled(false); + findPreference.setSummary(getResources().getString(R.string.no_equalizer)); + } else { + findPreference.setEnabled(true); + } + findPreference.setOnPreferenceClickListener(preference -> { + //noinspection ConstantConditions + NavigationUtil.openEqualizer(getActivity()); + return true; + }); + + + } + + private boolean hasEqualizer() { + final Intent effects = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL); + //noinspection ConstantConditions + PackageManager pm = getActivity().getPackageManager(); + ResolveInfo ri = pm.resolveActivity(effects, 0); + return ri != null; + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.pref_audio); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ImageSettingFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ImageSettingFragment.java new file mode 100644 index 00000000..d34c0a32 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ImageSettingFragment.java @@ -0,0 +1,28 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.os.Bundle; +import android.support.v7.preference.Preference; + +import code.name.monkey.retromusic.R; + +/** + * @author Hemanth S (h4h13). + */ + +public class ImageSettingFragment extends AbsSettingsFragment { + @Override + public void invalidateSettings() { + final Preference autoDownloadImagesPolicy = findPreference("auto_download_images_policy"); + setSummary(autoDownloadImagesPolicy); + autoDownloadImagesPolicy.setOnPreferenceChangeListener((preference, o) -> { + setSummary(autoDownloadImagesPolicy, o); + return true; + }); + + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.pref_images); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.java new file mode 100644 index 00000000..9c505166 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/MainSettingsFragment.java @@ -0,0 +1,85 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +import butterknife.BindView; +import butterknife.BindViews; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.ui.activities.SettingsActivity; +import code.name.monkey.retromusic.views.IconImageView; + +public class MainSettingsFragment extends Fragment { + + Unbinder unbinder; + @BindViews({R.id.general_settings_icon, R.id.audio_settings_icon, + R.id.now_playing_settings_icon, R.id.image_settings_icon, + R.id.notification_settings_icon, R.id.other_settings_icon}) + List icons; + ButterKnife.Action apply = (view, index) -> { + //noinspection ConstantConditions + ((IconImageView) view).setColorFilter(ThemeStore.accentColor(getContext()), PorterDuff.Mode.SRC_IN); + }; + @BindView(R.id.container) + ViewGroup container; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View layout = inflater.inflate(R.layout.fragment_main_settings, container, false); + unbinder = ButterKnife.bind(this, layout); + ButterKnife.apply(icons, apply); + return layout; + } + + + @Override + public void onDestroyView() { + super.onDestroyView(); + unbinder.unbind(); + } + + @OnClick({R.id.general_settings, R.id.audio_settings, R.id.now_playing_settings, + R.id.image_settings, R.id.notification_settings, R.id.other_settings}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.general_settings: + inflateFragment(new ThemeSettingsFragment()); + break; + case R.id.audio_settings: + inflateFragment(new AudioSettings()); + break; + case R.id.now_playing_settings: + inflateFragment(new NowPlayingSettingsFragment()); + break; + case R.id.image_settings: + inflateFragment(new ImageSettingFragment()); + break; + case R.id.notification_settings: + inflateFragment(new NotificationSettingsFragment()); + break; + case R.id.other_settings: + inflateFragment(new OtherSettingsFragment()); + break; + } + } + + private void inflateFragment(Fragment fragment) { + if (getActivity() != null) { + ((SettingsActivity) getActivity()).setupFragment(fragment); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NotificationSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NotificationSettingsFragment.java new file mode 100644 index 00000000..e2d6f2b8 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NotificationSettingsFragment.java @@ -0,0 +1,43 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.os.Build; +import android.os.Bundle; +import android.support.v7.preference.TwoStatePreference; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.service.MusicService; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class NotificationSettingsFragment extends AbsSettingsFragment { + @Override + public void invalidateSettings() { + final TwoStatePreference classicNotification = (TwoStatePreference) findPreference("classic_notification"); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + classicNotification.setVisible(false); + } else { + classicNotification.setChecked(PreferenceUtil.getInstance(getActivity()).classicNotification()); + classicNotification.setOnPreferenceChangeListener((preference, newValue) -> { + // Save preference + PreferenceUtil.getInstance(getActivity()).setClassicNotification((Boolean) newValue); + + final MusicService service = MusicPlayerRemote.musicService; + if (service != null) { + service.initNotification(); + service.updateNotification(); + } + + return true; + }); + } + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.pref_notification); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NowPlayingSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NowPlayingSettingsFragment.java new file mode 100644 index 00000000..eab599de --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/NowPlayingSettingsFragment.java @@ -0,0 +1,92 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.preference.TwoStatePreference; +import android.view.View; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class NowPlayingSettingsFragment extends AbsSettingsFragment implements + SharedPreferences.OnSharedPreferenceChangeListener { + + @SuppressWarnings("ConstantConditions") + @Override + public void invalidateSettings() { + updateNowPlayingScreenSummary(); + + final TwoStatePreference cornerWindow = (TwoStatePreference) findPreference("corner_window"); + cornerWindow.setOnPreferenceChangeListener((preference, newValue) -> { + if ((Boolean) newValue && !RetroApplication.isProVersion()) { + showProToastAndNavigate(getActivity().getString(R.string.pref_title_round_corners)); + return false; + } + getActivity().recreate(); + return true; + }); + final TwoStatePreference carouselEffect = (TwoStatePreference) findPreference( + "carousel_effect"); + carouselEffect.setOnPreferenceChangeListener((preference, newValue) -> { + if ((Boolean) newValue && !RetroApplication.isProVersion()) { + showProToastAndNavigate( + getActivity().getString(R.string.pref_title_toggle_carousel_effect)); + return false; + } + return true; + }); + + final TwoStatePreference toggleFullScreen = (TwoStatePreference) findPreference( + "toggle_full_screen"); + toggleFullScreen.setOnPreferenceChangeListener((preference, newValue) -> { + getActivity().recreate(); + return true; + }); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.pref_now_playing_screen); + addPreferencesFromResource(R.xml.pref_ui); + addPreferencesFromResource(R.xml.pref_window); + addPreferencesFromResource(R.xml.pref_lockscreen); + } + + private void updateNowPlayingScreenSummary() { + //noinspection ConstantConditions + findPreference("now_playing_screen_id") + .setSummary(PreferenceUtil.getInstance(getActivity()).getNowPlayingScreen().titleRes); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + //noinspection ConstantConditions + PreferenceUtil.getInstance(getContext()).registerOnSharedPreferenceChangedListener(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + //noinspection ConstantConditions + PreferenceUtil.getInstance(getContext()).unregisterOnSharedPreferenceChangedListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + switch (key) { + case PreferenceUtil.NOW_PLAYING_SCREEN_ID: + updateNowPlayingScreenSummary(); + break; + case PreferenceUtil.CIRCULAR_ALBUM_ART: + case PreferenceUtil.CAROUSEL_EFFECT: + invalidateSettings(); + break; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/OtherSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/OtherSettingsFragment.java new file mode 100644 index 00000000..4099daa1 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/OtherSettingsFragment.java @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.os.Bundle; + +import code.name.monkey.retromusic.R; + +/** + * @author Hemanth S (h4h13). + */ + +public class OtherSettingsFragment extends AbsSettingsFragment { + @Override + public void invalidateSettings() { + + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.pref_blacklist); + addPreferencesFromResource(R.xml.pref_playlists); + addPreferencesFromResource(R.xml.pref_advanced); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ThemeSettingsFragment.java b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ThemeSettingsFragment.java new file mode 100644 index 00000000..b33aaedd --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/settings/ThemeSettingsFragment.java @@ -0,0 +1,143 @@ +package code.name.monkey.retromusic.ui.fragments.settings; + +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.support.v7.preference.Preference; +import android.support.v7.preference.TwoStatePreference; + +import com.afollestad.materialdialogs.color.ColorChooserDialog; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEColorPreference; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.VersionUtils; +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager; +import code.name.monkey.retromusic.ui.activities.SettingsActivity; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * @author Hemanth S (h4h13). + */ + +public class ThemeSettingsFragment extends AbsSettingsFragment { + + @Override + public void invalidateSettings() { + + final ATEColorPreference primaryColorPref = (ATEColorPreference) findPreference( + "primary_color"); + //noinspection ConstantConditions + primaryColorPref.setVisible(PreferenceUtil.getInstance(getActivity()).getGeneralTheme() == R.style.Theme_RetroMusic_Color); + final int primaryColor = ThemeStore.primaryColor(getActivity()); + primaryColorPref.setColor(primaryColor, ColorUtil.darkenColor(primaryColor)); + primaryColorPref.setOnPreferenceClickListener(preference -> { + new ColorChooserDialog.Builder(getActivity(), R.string.primary_color) + .accentMode(false) + .allowUserColorInput(true) + .allowUserColorInputAlpha(false) + .preselect(primaryColor) + .show(getActivity()); + return true; + }); + + final Preference generalTheme = findPreference("general_theme"); + setSummary(generalTheme); + generalTheme.setOnPreferenceChangeListener((preference, newValue) -> { + String theme = (String) newValue; + + if (theme.equals("color") && !RetroApplication.isProVersion()) { + primaryColorPref.setVisible(false); + showProToastAndNavigate("Color theme"); + return false; + } else { + primaryColorPref.setVisible(true); + } + + setSummary(generalTheme, newValue); + + + switch (theme) { + case "light": + ThemeStore.editTheme(getContext()).primaryColor(Color.WHITE).commit(); + break; + case "black": + ThemeStore.editTheme(getContext()).primaryColor(Color.BLACK).commit(); + break; + case "dark": + ThemeStore.editTheme(getContext()).primaryColor(ContextCompat.getColor(getContext(), R.color.md_grey_900)).commit(); + break; + case "color": + ThemeStore.editTheme(getContext()).primaryColor(ContextCompat.getColor(getContext(), R.color.md_blue_grey_800)).commit(); + break; + } + + ThemeStore.editTheme(getActivity()) + .activityTheme(PreferenceUtil.getThemeResFromPrefValue(theme)) + .commit(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + getActivity().setTheme(PreferenceUtil.getThemeResFromPrefValue(theme)); + new DynamicShortcutManager(getActivity()).updateDynamicShortcuts(); + } + getActivity().recreate(); + //invalidateSettings(); + return true; + }); + + ATEColorPreference accentColorPref = (ATEColorPreference) findPreference("accent_color"); + final int accentColor = ThemeStore.accentColor(getActivity()); + accentColorPref.setColor(accentColor, ColorUtil.darkenColor(accentColor)); + + accentColorPref.setOnPreferenceClickListener(preference -> { + new ColorChooserDialog.Builder(((SettingsActivity) getActivity()), R.string.accent_color) + .accentMode(true) + .allowUserColorInput(true) + .allowUserColorInputAlpha(false) + .preselect(accentColor) + .show(getActivity()); + return true; + }); + + TwoStatePreference colorNavBar = (TwoStatePreference) findPreference( + "should_color_navigation_bar"); + if (!VersionUtils.hasLollipop()) { + colorNavBar.setEnabled(false); + colorNavBar.setSummary(R.string.pref_only_lollipop); + } else { + colorNavBar.setChecked(ThemeStore.coloredNavigationBar(getActivity())); + colorNavBar.setOnPreferenceChangeListener((preference, newValue) -> { + ThemeStore.editTheme(getActivity()) + .coloredNavigationBar((Boolean) newValue) + .commit(); + getActivity().recreate(); + return true; + }); + } + + TwoStatePreference colorAppShortcuts = (TwoStatePreference) findPreference( + "should_color_app_shortcuts"); + if (!VersionUtils.hasNougatMR()) { + colorAppShortcuts.setVisible(false); + } else { + colorAppShortcuts.setChecked(PreferenceUtil.getInstance(getActivity()).coloredAppShortcuts()); + colorAppShortcuts.setOnPreferenceChangeListener((preference, newValue) -> { + // Save preference + PreferenceUtil.getInstance(getActivity()).setColoredAppShortcuts((Boolean) newValue); + // Update app shortcuts + new DynamicShortcutManager(getActivity()).updateDynamicShortcuts(); + + return true; + }); + } + + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.pref_general); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/AnimationUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/AnimationUtil.java new file mode 100644 index 00000000..27a8caa8 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/AnimationUtil.java @@ -0,0 +1,23 @@ +package code.name.monkey.retromusic.util; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.animation.AnimationUtils; +import android.view.animation.LayoutAnimationController; +import code.name.monkey.retromusic.R; + +/** + * @author Hemanth S (h4h13). + */ +public class AnimationUtil { + + public static void runLayoutAnimation(final RecyclerView recyclerView) { + final Context context = recyclerView.getContext(); + final LayoutAnimationController controller = + AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_slide_from_bottom); + + recyclerView.setLayoutAnimation(controller); + recyclerView.getAdapter().notifyDataSetChanged(); + recyclerView.scheduleLayoutAnimation(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java new file mode 100644 index 00000000..f747be56 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/ArtistSignatureUtil.java @@ -0,0 +1,41 @@ +package code.name.monkey.retromusic.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +import com.bumptech.glide.signature.StringSignature; + + +public class ArtistSignatureUtil { + private static final String ARTIST_SIGNATURE_PREFS = "artist_signatures"; + + private static ArtistSignatureUtil sInstance; + + private final SharedPreferences mPreferences; + + private ArtistSignatureUtil(@NonNull final Context context) { + mPreferences = context.getSharedPreferences(ARTIST_SIGNATURE_PREFS, Context.MODE_PRIVATE); + } + + public static ArtistSignatureUtil getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new ArtistSignatureUtil(context.getApplicationContext()); + } + return sInstance; + } + + @SuppressLint("CommitPrefEdits") + public void updateArtistSignature(String artistName) { + mPreferences.edit().putLong(artistName, System.currentTimeMillis()).apply(); + } + + public long getArtistSignatureRaw(String artistName) { + return mPreferences.getLong(artistName, 0); + } + + public StringSignature getArtistSignature(String artistName) { + return new StringSignature(String.valueOf(getArtistSignatureRaw(artistName))); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/CalendarUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/CalendarUtil.java new file mode 100644 index 00000000..cd32f330 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/CalendarUtil.java @@ -0,0 +1,117 @@ +package code.name.monkey.retromusic.util; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +/** + * @author Eugene Cheung (arkon) + */ +public class CalendarUtil { + private static final long MS_PER_MINUTE = 60 * 1000; + private static final long MS_PER_DAY = 24 * 60 * MS_PER_MINUTE; + + private Calendar calendar; + + public CalendarUtil() { + this.calendar = Calendar.getInstance(); + } + + /** + * Returns the time elapsed so far today in milliseconds. + * + * @return Time elapsed today in milliseconds. + */ + public long getElapsedToday() { + // Time elapsed so far today + return (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * MS_PER_MINUTE + + calendar.get(Calendar.SECOND) * 1000 + + calendar.get(Calendar.MILLISECOND); + } + + /** + * Returns the time elapsed so far this week in milliseconds. + * + * @return Time elapsed this week in milliseconds. + */ + public long getElapsedWeek() { + // Today + days passed this week + long elapsed = getElapsedToday(); + + final int passedWeekdays = calendar.get(Calendar.DAY_OF_WEEK) - 1 - calendar.getFirstDayOfWeek(); + if (passedWeekdays > 0) { + elapsed += passedWeekdays * MS_PER_DAY; + } + + return elapsed; + } + + /** + * Returns the time elapsed so far this month in milliseconds. + * + * @return Time elapsed this month in milliseconds. + */ + public long getElapsedMonth() { + // Today + rest of this month + return getElapsedToday() + + ((calendar.get(Calendar.DAY_OF_MONTH) - 1) * MS_PER_DAY); + } + + /** + * Returns the time elapsed so far this month and the last numMonths months in milliseconds. + * + * @param numMonths Additional number of months prior to the current month to calculate. + * @return Time elapsed this month and the last numMonths months in milliseconds. + */ + public long getElapsedMonths(int numMonths) { + // Today + rest of this month + long elapsed = getElapsedMonth(); + + // Previous numMonths months + int month = calendar.get(Calendar.MONTH); + int year = calendar.get(Calendar.YEAR); + for (int i = 0; i < numMonths; i++) { + month--; + + if (month < Calendar.JANUARY) { + month = Calendar.DECEMBER; + year--; + } + + elapsed += getDaysInMonth(year, month) * MS_PER_DAY; + } + + return elapsed; + } + + /** + * Returns the time elapsed so far this year in milliseconds. + * + * @return Time elapsed this year in milliseconds. + */ + public long getElapsedYear() { + // Today + rest of this month + previous months until January + long elapsed = getElapsedMonth(); + + int month = calendar.get(Calendar.MONTH) - 1; + int year = calendar.get(Calendar.YEAR); + while (month > Calendar.JANUARY) { + elapsed += getDaysInMonth(year, month) * MS_PER_DAY; + + month--; + } + + return elapsed; + } + + /** + * Gets the number of days for the given month in the given year. + * + * @param year The year. + * @param month The month (1 - 12). + * @return The days in that month/year. + */ + private int getDaysInMonth(int year, int month) { + final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1); + return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ColorUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtils.java new file mode 100755 index 00000000..48854454 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/ColorUtils.java @@ -0,0 +1,64 @@ +package code.name.monkey.retromusic.util; + +import android.graphics.Color; +import android.support.annotation.ColorInt; + +public class ColorUtils { + + public static boolean isColorLight(@ColorInt int color) { + return getColorDarkness(color) < 0.5; + } + + private static double getColorDarkness(@ColorInt int color) { + if (color == Color.BLACK) + return 1.0; + else if (color == Color.WHITE || color == Color.TRANSPARENT) + return 0.0; + else + return (1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255); + } + + @ColorInt + public static int getInverseColor(@ColorInt int color) { + return (0xFFFFFF - color) | 0xFFFFFFFF; + } + + public static boolean isColorSaturated(@ColorInt int color) { + double max = Math.max(0.299 * Color.red(color), Math.max(0.587 * Color.green(color), 0.114 * Color.blue(color))); + double min = Math.min(0.299 * Color.red(color), Math.min(0.587 * Color.green(color), 0.114 * Color.blue(color))); + double diff = Math.abs(max - min); + return diff > 20; + } + + @ColorInt + public static int getMixedColor(@ColorInt int color1, @ColorInt int color2) { + return Color.rgb( + (Color.red(color1) + Color.red(color2)) / 2, + (Color.green(color1) + Color.green(color2)) / 2, + (Color.blue(color1) + Color.blue(color2)) / 2 + ); + } + + public static double getDifference(@ColorInt int color1, @ColorInt int color2) { + double diff = Math.abs(0.299 * (Color.red(color1) - Color.red(color2))); + diff += Math.abs(0.587 * (Color.green(color1) - Color.green(color2))); + diff += Math.abs(0.114 * (Color.blue(color1) - Color.blue(color2))); + return diff; + } + + @ColorInt + public static int getReadableText(@ColorInt int textColor, @ColorInt int backgroundColor) { + return getReadableText(textColor, backgroundColor, 100); + } + + @ColorInt + public static int getReadableText(@ColorInt int textColor, @ColorInt int backgroundColor, int difference) { + boolean isLight = isColorLight(backgroundColor); + for (int i = 0; getDifference(textColor, backgroundColor) < difference && i < 100; i++) { + textColor = getMixedColor(textColor, isLight ? Color.BLACK : Color.WHITE); + } + + return textColor; + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java b/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java new file mode 100644 index 00000000..eb8d6c0f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/Compressor.java @@ -0,0 +1,90 @@ +package code.name.monkey.retromusic.util; + +import android.content.Context; +import android.graphics.Bitmap; + +import java.io.File; +import java.io.IOException; + +import io.reactivex.Flowable; + +/** + * Created on : June 18, 2016 + * Author : zetbaitsu + * Name : Zetra + * GitHub : https://github.com/zetbaitsu + */ +public class Compressor { + //max width and height values of the compressed image is taken as 612x816 + private int maxWidth = 612; + private int maxHeight = 816; + private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG; + private int quality = 80; + private String destinationDirectoryPath; + + public Compressor(Context context) { + destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images"; + } + + public Compressor setMaxWidth(int maxWidth) { + this.maxWidth = maxWidth; + return this; + } + + public Compressor setMaxHeight(int maxHeight) { + this.maxHeight = maxHeight; + return this; + } + + public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) { + this.compressFormat = compressFormat; + return this; + } + + public Compressor setQuality(int quality) { + this.quality = quality; + return this; + } + + public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) { + this.destinationDirectoryPath = destinationDirectoryPath; + return this; + } + + public File compressToFile(File imageFile) throws IOException { + return compressToFile(imageFile, imageFile.getName()); + } + + public File compressToFile(File imageFile, String compressedFileName) throws IOException { + return ImageUtil.compressImage(imageFile, maxWidth, maxHeight, compressFormat, quality, + destinationDirectoryPath + File.separator + compressedFileName); + } + + public Bitmap compressToBitmap(File imageFile) throws IOException { + return ImageUtil.decodeSampledBitmapFromFile(imageFile, maxWidth, maxHeight); + } + + public Flowable compressToFileAsFlowable(final File imageFile) { + return compressToFileAsFlowable(imageFile, imageFile.getName()); + } + + public Flowable compressToFileAsFlowable(final File imageFile, final String compressedFileName) { + return Flowable.defer(() -> { + try { + return Flowable.just(compressToFile(imageFile, compressedFileName)); + } catch (IOException e) { + return Flowable.error(e); + } + }); + } + + public Flowable compressToBitmapAsFlowable(final File imageFile) { + return Flowable.defer(() -> { + try { + return Flowable.just(compressToBitmap(imageFile)); + } catch (IOException e) { + return Flowable.error(e); + } + }); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.java new file mode 100644 index 00000000..ca369a9d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/CustomArtistImageUtil.java @@ -0,0 +1,137 @@ +package code.name.monkey.retromusic.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; + +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.model.Artist; + + +public class CustomArtistImageUtil { + private static final String CUSTOM_ARTIST_IMAGE_PREFS = "custom_artist_image"; + private static final String FOLDER_NAME = "/custom_artist_images/"; + + private static CustomArtistImageUtil sInstance; + + private final SharedPreferences mPreferences; + + private CustomArtistImageUtil(@NonNull final Context context) { + mPreferences = context.getApplicationContext().getSharedPreferences(CUSTOM_ARTIST_IMAGE_PREFS, Context.MODE_PRIVATE); + } + + public static CustomArtistImageUtil getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new CustomArtistImageUtil(context.getApplicationContext()); + } + return sInstance; + } + + private static String getFileName(Artist artist) { + String artistName = artist.getName(); + if (artistName == null) + artistName = ""; + // replace everything that is not a letter or a number with _ + artistName = artistName.replaceAll("[^a-zA-Z0-9]", "_"); + return String.format(Locale.US, "#%d#%s.jpeg", artist.getId(), artistName); + } + + public static File getFile(Artist artist) { + File dir = new File(RetroApplication.getInstance().getFilesDir(), FOLDER_NAME); + return new File(dir, getFileName(artist)); + } + + public void setCustomArtistImage(final Artist artist, Uri uri) { + Glide.with(RetroApplication.getInstance()) + .load(uri) + .asBitmap() + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .into(new SimpleTarget() { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + e.printStackTrace(); + Toast.makeText(RetroApplication.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); + } + + @SuppressLint("StaticFieldLeak") + @Override + public void onResourceReady(final Bitmap resource, GlideAnimation glideAnimation) { + new AsyncTask() { + @SuppressLint("ApplySharedPref") + @Override + protected Void doInBackground(Void... params) { + File dir = new File(RetroApplication.getInstance().getFilesDir(), FOLDER_NAME); + if (!dir.exists()) { + if (!dir.mkdirs()) { // create the folder + return null; + } + } + File file = new File(dir, getFileName(artist)); + + boolean succesful = false; + try { + OutputStream os = new BufferedOutputStream(new FileOutputStream(file)); + succesful = ImageUtil.resizeBitmap(resource, 2048).compress(Bitmap.CompressFormat.JPEG, 100, os); + os.close(); + } catch (IOException e) { + Toast.makeText(RetroApplication.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); + } + + if (succesful) { + mPreferences.edit().putBoolean(getFileName(artist), true).commit(); + ArtistSignatureUtil.getInstance(RetroApplication.getInstance()).updateArtistSignature(artist.getName()); + RetroApplication.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload + } + return null; + } + }.execute(); + } + }); + } + + @SuppressLint("StaticFieldLeak") + public void resetCustomArtistImage(final Artist artist) { + new AsyncTask() { + @SuppressLint("ApplySharedPref") + @Override + protected Void doInBackground(Void... params) { + mPreferences.edit().putBoolean(getFileName(artist), false).commit(); + ArtistSignatureUtil.getInstance(RetroApplication.getInstance()).updateArtistSignature(artist.getName()); + RetroApplication.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload + + File file = getFile(artist); + if (!file.exists()) { + return null; + } else { + file.delete(); + } + return null; + } + }.execute(); + } + + // shared prefs saves us many IO operations + public boolean hasCustomArtistImage(Artist artist) { + return mPreferences.getBoolean(getFileName(artist), false); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/DensityUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/DensityUtil.java new file mode 100644 index 00000000..78aedd21 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/DensityUtil.java @@ -0,0 +1,54 @@ +package code.name.monkey.retromusic.util; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.util.TypedValue; + +/** + * Created by hefuyi on 16/7/30. + */ +public class DensityUtil { + + public static int getScreenHeight(Context context) { + DisplayMetrics displayMetrics = new DisplayMetrics(); + ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + return displayMetrics.heightPixels; + } + + public static int getScreenWidth(Context context) { + DisplayMetrics displayMetrics = new DisplayMetrics(); + ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + return displayMetrics.widthPixels; + } + + public static int dip2px(Context context, float dpVale) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpVale * scale + 0.5f); + } + + public static int getStatusBarHeight(Context context) { + Resources resources = context.getResources(); + int resourcesId = resources.getIdentifier("status_bar_height", "dimen", "android"); + int height = resources.getDimensionPixelSize(resourcesId); + return height; + } + + /** + * Converts sp to px + * + * @param context Context + * @param sp the value in sp + * @return int + */ + public static int dip2sp(Context context, float sp) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics()); + } + + public static int px2dip(Context context, float pxValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (pxValue / scale + 0.5f); + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java new file mode 100644 index 00000000..4ac97d3c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java @@ -0,0 +1,251 @@ +package code.name.monkey.retromusic.util; + +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.webkit.MimeTypeMap; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.loaders.SortedCursor; +import code.name.monkey.retromusic.model.Song; +import io.reactivex.Observable; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + + +public final class FileUtil { + + private FileUtil() { + } + + public static byte[] readBytes(InputStream stream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int count; + while ((count = stream.read(buffer)) != -1) { + baos.write(buffer, 0, count); + } + stream.close(); + return baos.toByteArray(); + } + + @NonNull + public static Observable> matchFilesWithMediaStore(@NonNull Context context, + @Nullable List files) { + return SongLoader.getSongs(makeSongCursor(context, files)); + } + + public static String safeGetCanonicalPath(File file) { + try { + return file.getCanonicalPath(); + } catch (IOException e) { + e.printStackTrace(); + return file.getAbsolutePath(); + } + } + + @Nullable + public static SortedCursor makeSongCursor(@NonNull final Context context, + @Nullable final List files) { + String selection = null; + String[] paths = null; + + if (files != null) { + paths = toPathArray(files); + + if (files.size() > 0 + && files.size() < 999) { // 999 is the max amount Androids SQL implementation can handle. + selection = + MediaStore.Audio.AudioColumns.DATA + " IN (" + makePlaceholders(files.size()) + ")"; + } + } + + Cursor songCursor = SongLoader + .makeSongCursor(context, selection, selection == null ? null : paths); + + return songCursor == null ? null + : new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA); + } + + private static String makePlaceholders(int len) { + StringBuilder sb = new StringBuilder(len * 2 - 1); + sb.append("?"); + for (int i = 1; i < len; i++) { + sb.append(",?"); + } + return sb.toString(); + } + + @Nullable + private static String[] toPathArray(@Nullable List files) { + if (files != null) { + String[] paths = new String[files.size()]; + for (int i = 0; i < files.size(); i++) { + /*try { + paths[i] = files.get(i).getCanonicalPath(); // canonical path is important here because we want to compare the path with the media store entry later + } catch (IOException e) { + e.printStackTrace(); + paths[i] = files.get(i).getPath(); + }*/ + paths[i] = safeGetCanonicalPath(files.get(i)); + } + return paths; + } + return null; + } + + @NonNull + public static List listFiles(@NonNull File directory, @Nullable FileFilter fileFilter) { + List fileList = new LinkedList<>(); + File[] found = directory.listFiles(fileFilter); + if (found != null) { + Collections.addAll(fileList, found); + } + return fileList; + } + + @NonNull + public static List listFilesDeep(@NonNull File directory, @Nullable FileFilter fileFilter) { + List files = new LinkedList<>(); + internalListFilesDeep(files, directory, fileFilter); + return files; + } + + @NonNull + public static List listFilesDeep(@NonNull Collection files, + @Nullable FileFilter fileFilter) { + List resFiles = new LinkedList<>(); + for (File file : files) { + if (file.isDirectory()) { + internalListFilesDeep(resFiles, file, fileFilter); + } else if (fileFilter == null || fileFilter.accept(file)) { + resFiles.add(file); + } + } + return resFiles; + } + + private static void internalListFilesDeep(@NonNull Collection files, + @NonNull File directory, @Nullable FileFilter fileFilter) { + File[] found = directory.listFiles(fileFilter); + + if (found != null) { + for (File file : found) { + if (file.isDirectory()) { + internalListFilesDeep(files, file, fileFilter); + } else { + files.add(file); + } + } + } + } + + public static boolean fileIsMimeType(File file, String mimeType, MimeTypeMap mimeTypeMap) { + if (mimeType == null || mimeType.equals("*/*")) { + return true; + } else { + // get the file mime type + String filename = file.toURI().toString(); + int dotPos = filename.lastIndexOf('.'); + if (dotPos == -1) { + return false; + } + String fileExtension = filename.substring(dotPos + 1).toLowerCase(); + String fileType = mimeTypeMap.getMimeTypeFromExtension(fileExtension); + if (fileType == null) { + return false; + } + // check the 'type/subtype' pattern + if (fileType.equals(mimeType)) { + return true; + } + // check the 'type/*' pattern + int mimeTypeDelimiter = mimeType.lastIndexOf('/'); + if (mimeTypeDelimiter == -1) { + return false; + } + String mimeTypeMainType = mimeType.substring(0, mimeTypeDelimiter); + String mimeTypeSubtype = mimeType.substring(mimeTypeDelimiter + 1); + if (!mimeTypeSubtype.equals("*")) { + return false; + } + int fileTypeDelimiter = fileType.lastIndexOf('/'); + if (fileTypeDelimiter == -1) { + return false; + } + String fileTypeMainType = fileType.substring(0, fileTypeDelimiter); + if (fileTypeMainType.equals(mimeTypeMainType)) { + return true; + } + return fileTypeMainType.equals(mimeTypeMainType); + } + } + + public static String stripExtension(String str) { + if (str == null) { + return null; + } + int pos = str.lastIndexOf('.'); + if (pos == -1) { + return str; + } + return str.substring(0, pos); + } + + public static String readFromStream(InputStream is) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + if (sb.length() > 0) { + sb.append("\n"); + } + sb.append(line); + } + reader.close(); + return sb.toString(); + } + + public static String read(File file) throws Exception { + FileInputStream fin = new FileInputStream(file); + String ret = readFromStream(fin); + fin.close(); + return ret; + } + + public static boolean isExternalMemoryAvailable() { + Boolean isSDPresent = Environment.getExternalStorageState() + .equals(android.os.Environment.MEDIA_MOUNTED); + Boolean isSDSupportedDevice = Environment.isExternalStorageRemovable(); + + if (isSDSupportedDevice && isSDPresent) { + // yes SD-card is present + return true; + } else { + return false; + // Sorry + } + } + + public static File safeGetCanonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + e.printStackTrace(); + return file.getAbsoluteFile(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java new file mode 100644 index 00000000..bb0a8f04 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/ImageUtil.java @@ -0,0 +1,222 @@ +package code.name.monkey.retromusic.util; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.media.ExifInterface; +import android.support.annotation.NonNull; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Created on : June 18, 2016 Author : zetbaitsu Name : Zetra GitHub : + * https://github.com/zetbaitsu + */ +public class ImageUtil { + + private static final int TOLERANCE = 20; + // Alpha amount for which values below are considered transparent. + private static final int ALPHA_TOLERANCE = 50; + private static int[] mTempBuffer; + + private ImageUtil() { + + } + + public static boolean isGrayscale(Bitmap bitmap) { + final int height = bitmap.getHeight(); + final int width = bitmap.getWidth(); + int size = height * width; + ensureBufferSize(size); + bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); + for (int i = 0; i < size; i++) { + if (!isGrayscale(mTempBuffer[i])) { + return false; + } + } + return true; + } + + /** + * Makes sure that {@code mTempBuffer} has at least length {@code size}. + */ + private static void ensureBufferSize(int size) { + if (mTempBuffer == null || mTempBuffer.length < size) { + mTempBuffer = new int[size]; + } + } + + public static Bitmap setBitmapColor(Bitmap bitmap, int color) { + Bitmap result = Bitmap + .createBitmap(bitmap, 0, 0, bitmap.getWidth() - 1, bitmap.getHeight() - 1); + Paint paint = new Paint(); + paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)); + + Canvas canvas = new Canvas(result); + canvas.drawBitmap(result, 0, 0, paint); + + return result; + } + + public static boolean isGrayscale(int color) { + int alpha = 0xFF & (color >> 24); + if (alpha < ALPHA_TOLERANCE) { + return true; + } + int r = 0xFF & (color >> 16); + int g = 0xFF & (color >> 8); + int b = 0xFF & color; + return Math.abs(r - g) < TOLERANCE + && Math.abs(r - b) < TOLERANCE + && Math.abs(g - b) < TOLERANCE; + } // Amount (max is 255) that two channels can differ before the color is no longer "gray". + + public static Bitmap resizeBitmap(@NonNull Bitmap src, int maxForSmallerSize) { + int width = src.getWidth(); + int height = src.getHeight(); + + final int dstWidth; + final int dstHeight; + + if (width < height) { + if (maxForSmallerSize >= width) { + return src; + } + float ratio = (float) height / width; + dstWidth = maxForSmallerSize; + dstHeight = Math.round(maxForSmallerSize * ratio); + } else { + if (maxForSmallerSize >= height) { + return src; + } + float ratio = (float) width / height; + dstWidth = Math.round(maxForSmallerSize * ratio); + dstHeight = maxForSmallerSize; + } + + return Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); + } + + public static int calculateInSampleSize(int width, int height, int reqWidth) { + // setting reqWidth matching to desired 1:1 ratio and screen-size + if (width < height) { + reqWidth = (height / width) * reqWidth; + } else { + reqWidth = (width / height) * reqWidth; + } + + int inSampleSize = 1; + + if (height > reqWidth || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqWidth + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + static File compressImage(File imageFile, int reqWidth, int reqHeight, + Bitmap.CompressFormat compressFormat, int quality, String destinationPath) + throws IOException { + FileOutputStream fileOutputStream = null; + File file = new File(destinationPath).getParentFile(); + if (!file.exists()) { + file.mkdirs(); + } + try { + fileOutputStream = new FileOutputStream(destinationPath); + // write the compressed bitmap at the destination specified by destinationPath. + decodeSampledBitmapFromFile(imageFile, reqWidth, reqHeight) + .compress(compressFormat, quality, fileOutputStream); + } finally { + if (fileOutputStream != null) { + fileOutputStream.flush(); + fileOutputStream.close(); + } + } + + return new File(destinationPath); + } + + static Bitmap decodeSampledBitmapFromFile(File imageFile, int reqWidth, int reqHeight) + throws IOException { + // First decode with inJustDecodeBounds=true to check dimensions + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + + Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); + + //check the rotation of the image and display it properly + ExifInterface exif; + exif = new ExifInterface(imageFile.getAbsolutePath()); + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); + Matrix matrix = new Matrix(); + if (orientation == 6) { + matrix.postRotate(90); + } else if (orientation == 3) { + matrix.postRotate(180); + } else if (orientation == 8) { + matrix.postRotate(270); + } + scaledBitmap = Bitmap + .createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, + true); + return scaledBitmap; + } + + private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, + int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + public static Bitmap getResizedBitmap(Bitmap image, int maxSize) { + int width = image.getWidth(); + int height = image.getHeight(); + + float bitmapRatio = (float) width / (float) height; + if (bitmapRatio > 1) { + width = maxSize; + height = (int) (width / bitmapRatio); + } else { + height = maxSize; + width = (int) (height * bitmapRatio); + } + return Bitmap.createScaledBitmap(image, width, height, true); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/LastFMUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/LastFMUtil.java new file mode 100755 index 00000000..d7d6fe1a --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/LastFMUtil.java @@ -0,0 +1,69 @@ +package code.name.monkey.retromusic.util; + +import code.name.monkey.retromusic.rest.model.LastFmAlbum.Album.Image; +import code.name.monkey.retromusic.rest.model.LastFmArtist; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class LastFMUtil { + + public static String getLargestAlbumImageUrl(List list) { + Map hashMap = new HashMap(); + for (Image image : list) { + Object obj = null; + String size = image.getSize(); + if (size == null) { + obj = ImageSize.UNKNOWN; + } else { + try { + obj = ImageSize.valueOf(size.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException ignored) { + } + } + if (obj != null) { + hashMap.put(obj, image.getText()); + } + } + return getLargestImageUrl(hashMap); + } + + public static String getLargestArtistImageUrl(List list) { + Map hashMap = new HashMap(); + for (LastFmArtist.Artist.Image image : list) { + Object obj = null; + String size = image.getSize(); + if (size == null) { + obj = ImageSize.UNKNOWN; + } else { + try { + obj = ImageSize.valueOf(size.toUpperCase(Locale.ENGLISH)); + } catch (IllegalArgumentException ignored) { + } + } + if (obj != null) { + hashMap.put(obj, image.getText()); + } + } + return getLargestImageUrl(hashMap); + } + + private static String getLargestImageUrl(Map map) { + return map.containsKey(ImageSize.MEGA) ? map.get(ImageSize.MEGA) + : map.containsKey(ImageSize.EXTRALARGE) ? map.get(ImageSize.EXTRALARGE) + : map.containsKey(ImageSize.LARGE) ? map.get(ImageSize.LARGE) + : map.containsKey(ImageSize.MEDIUM) ? map.get(ImageSize.MEDIUM) + : map.containsKey(ImageSize.SMALL) ? map.get(ImageSize.SMALL) + : map.containsKey(ImageSize.UNKNOWN) ? map.get(ImageSize.UNKNOWN) : null; + } + + private enum ImageSize { + SMALL, + MEDIUM, + LARGE, + EXTRALARGE, + MEGA, + UNKNOWN + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java new file mode 100644 index 00000000..1af45380 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/LyricUtil.java @@ -0,0 +1,103 @@ +package code.name.monkey.retromusic.util; + +import android.util.Base64; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; + +/** + * Created by hefuyi on 2016/11/8. + */ + +public class LyricUtil { + + private static final String lrcRootPath = android.os.Environment + .getExternalStorageDirectory().toString() + "/RetroMusic/lyrics/"; + + public static File writeLrcToLoc(String title, String artist, String lrcContext) { + FileWriter writer = null; + try { + File file = new File(getLrcPath(title, artist)); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + writer = new FileWriter(getLrcPath(title, artist)); + writer.write(lrcContext); + return file; + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + try { + if (writer != null) + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static boolean deleteLrcFile(String title, String artist) { + File file = new File(getLrcPath(title, artist)); + return file.delete(); + } + + public static boolean isLrcFileExist(String title, String artist) { + File file = new File(getLrcPath(title, artist)); + return file.exists(); + } + + public static File getLocalLyricFile(String title, String artist) { + File file = new File(getLrcPath(title, artist)); + if (file.exists()) { + return file; + } else { + return new File("lyric file not exist"); + } + } + + private static String getLrcPath(String title, String artist) { + return lrcRootPath + title + " - " + artist + ".lrc"; + } + + public static String decryptBASE64(String str) { + if (str == null || str.length() == 0) { + return null; + } + try { + byte[] encode = str.getBytes("UTF-8"); + // base64 解密 + return new String(Base64.decode(encode, 0, encode.length, Base64.DEFAULT), "UTF-8"); + + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return null; + } + + public static String getStringFromFile(String title, String artist) throws Exception { + File file = new File(getLrcPath(title, artist)); + FileInputStream fin = new FileInputStream(file); + String ret = convertStreamToString(fin); + fin.close(); + return ret; + } + + private static String convertStreamToString(InputStream is) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.java new file mode 100644 index 00000000..49b1c0a7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.java @@ -0,0 +1,410 @@ +package code.name.monkey.retromusic.util; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.BaseColumns; +import android.provider.MediaStore; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.FileProvider; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Toast; + +import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.tag.FieldKey; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.loaders.PlaylistLoader; +import code.name.monkey.retromusic.loaders.SongLoader; +import code.name.monkey.retromusic.model.Artist; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.Song; +import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics; +import io.reactivex.Observable; + + +public class MusicUtil { + + public static final String TAG = MusicUtil.class.getSimpleName(); + private static Playlist playlist; + + public static Uri getMediaStoreAlbumCoverUri(int albumId) { + final Uri sArtworkUri = Uri + .parse("content://media/external/audio/albumart"); + + return ContentUris.withAppendedId(sArtworkUri, albumId); + } + + public static Uri getSongFileUri(int songId) { + return ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, songId); + } + + @NonNull + public static Intent createShareSongFileIntent(@NonNull final Song song, Context context) { + try { + + return new Intent() + .setAction(Intent.ACTION_SEND) + .putExtra(Intent.EXTRA_STREAM, + FileProvider.getUriForFile(context, + context.getApplicationContext().getPackageName(), + new File(song.data))) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .setType("audio/*"); + } catch (IllegalArgumentException e) { + // TODO the path is most likely not like /storage/emulated/0/... but something like /storage/28C7-75B0/... + e.printStackTrace(); + Toast.makeText(context, "Could not share this file, I'm aware of the issue.", + Toast.LENGTH_SHORT).show(); + return new Intent(); + } + } + + public static void setRingtone(@NonNull final Context context, final int id) { + final ContentResolver resolver = context.getContentResolver(); + final Uri uri = getSongFileUri(id); + try { + final ContentValues values = new ContentValues(2); + values.put(MediaStore.Audio.AudioColumns.IS_RINGTONE, "1"); + values.put(MediaStore.Audio.AudioColumns.IS_ALARM, "1"); + resolver.update(uri, values, null, null); + } catch (@NonNull final UnsupportedOperationException ignored) { + return; + } + + try { + Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + new String[]{MediaStore.MediaColumns.TITLE}, + BaseColumns._ID + "=?", + new String[]{String.valueOf(id)}, + null); + try { + if (cursor != null && cursor.getCount() == 1) { + cursor.moveToFirst(); + Settings.System.putString(resolver, Settings.System.RINGTONE, uri.toString()); + final String message = context + .getString(R.string.x_has_been_set_as_ringtone, cursor.getString(0)); + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } catch (SecurityException ignored) { + } + } + + @NonNull + public static String getArtistInfoString(@NonNull final Context context, + @NonNull final Artist artist) { + int albumCount = artist.getAlbumCount(); + int songCount = artist.getSongCount(); + String albumString = albumCount == 1 ? context.getResources().getString(R.string.album) + : context.getResources().getString(R.string.albums); + String songString = songCount == 1 ? context.getResources().getString(R.string.song) + : context.getResources().getString(R.string.songs); + return albumCount + " " + albumString + " • " + songCount + " " + songString; + } + + @NonNull + public static String getArtistInfoStringSmall(@NonNull final Context context, + @NonNull final Artist artist) { + int songCount = artist.getSongCount(); + String songString = songCount == 1 ? context.getResources().getString(R.string.song) + : context.getResources().getString(R.string.songs); + return songCount + " " + songString; + } + + @NonNull + public static String getPlaylistInfoString(@NonNull final Context context, + @NonNull List songs) { + final int songCount = songs.size(); + final String songString = songCount == 1 ? context.getResources().getString(R.string.song) + : context.getResources().getString(R.string.songs); + + long duration = 0; + for (int i = 0; i < songs.size(); i++) { + duration += songs.get(i).duration; + } + + return songCount + " " + songString + " • " + MusicUtil.getReadableDurationString(duration); + } + + public static String getReadableDurationString(long songDurationMillis) { + long minutes = (songDurationMillis / 1000) / 60; + long seconds = (songDurationMillis / 1000) % 60; + if (minutes < 60) { + return String.format(Locale.getDefault(), "%01d:%02d", minutes, seconds); + } else { + long hours = minutes / 60; + minutes = minutes % 60; + return String.format(Locale.getDefault(), "%d:%02d:%02d", hours, 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(@NonNull 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); + } + + @NonNull + public static File createAlbumArtFile() { + return new File(createAlbumArtDir(), String.valueOf(System.currentTimeMillis())); + } + + @NonNull + @SuppressWarnings("ResultOfMethodCallIgnored") + private static File createAlbumArtDir() { + File albumArtDir = new File(Environment.getExternalStorageDirectory(), "/albumthumbs/"); + if (!albumArtDir.exists()) { + albumArtDir.mkdirs(); + try { + new File(albumArtDir, ".nomedia").createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return albumArtDir; + } + + public static void deleteTracks(@NonNull final Activity activity, + @NonNull final List songs) { + final String[] projection = new String[]{ + BaseColumns._ID, MediaStore.MediaColumns.DATA + }; + final StringBuilder selection = new StringBuilder(); + selection.append(BaseColumns._ID + " IN ("); + for (int i = 0; i < songs.size(); i++) { + selection.append(songs.get(i).id); + if (i < songs.size() - 1) { + selection.append(","); + } + } + selection.append(")"); + + try { + final Cursor cursor = activity.getContentResolver().query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), + null, null); + if (cursor != null) { + // Step 1: Remove selected tracks from the current playlist, as well + // as from the album art cache + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + final int id = cursor.getInt(0); + SongLoader.getSong(activity, id).subscribe(song -> { + MusicPlayerRemote.removeFromQueue(song); + cursor.moveToNext(); + }); + } + + // Step 2: Remove selected tracks from the database + activity.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + selection.toString(), null); + + // Step 3: Remove files from card + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + final String name = cursor.getString(1); + try { // File.delete can throw a security exception + final File f = new File(name); + if (!f.delete()) { + // I'm not sure if we'd ever get here (deletion would + // have to fail, but no exception thrown) + Log.e("MusicUtils", "Failed to delete file " + name); + } + cursor.moveToNext(); + } catch (@NonNull final SecurityException ex) { + cursor.moveToNext(); + } catch (NullPointerException e) { + Log.e("MusicUtils", "Failed to find file " + name); + } + } + cursor.close(); + } + activity.getContentResolver().notifyChange(Uri.parse("content://media"), null); + Toast.makeText(activity, activity.getString(R.string.deleted_x_songs, songs.size()), + Toast.LENGTH_SHORT).show(); + } catch (SecurityException ignored) { + } + } + + public static void deleteAlbumArt(@NonNull 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); + } + + + @Nullable + public static String getLyrics(Song song) { + String lyrics = null; + + File file = new File(song.data); + + try { + lyrics = AudioFileIO.read(file).getTagOrCreateDefault().getFirst(FieldKey.LYRICS); + } catch (Exception e) { + e.printStackTrace(); + } + + if (lyrics == null || lyrics.trim().isEmpty() || !AbsSynchronizedLyrics + .isSynchronized(lyrics)) { + File dir = file.getAbsoluteFile().getParentFile(); + + if (dir != null && dir.exists() && dir.isDirectory()) { + String format = ".*%s.*\\.(lrc|txt)"; + String filename = Pattern.quote(FileUtil.stripExtension(file.getName())); + String songtitle = Pattern.quote(song.title); + + final ArrayList patterns = new ArrayList<>(); + patterns.add(Pattern.compile(String.format(format, filename), + Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE)); + patterns.add(Pattern.compile(String.format(format, songtitle), + Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE)); + + File[] files = dir.listFiles(f -> { + for (Pattern pattern : patterns) { + if (pattern.matcher(f.getName()).matches()) { + return true; + } + } + return false; + }); + + if (files != null && files.length > 0) { + for (File f : files) { + try { + String newLyrics = FileUtil.read(f); + if (newLyrics != null && !newLyrics.trim().isEmpty()) { + if (AbsSynchronizedLyrics.isSynchronized(newLyrics)) { + return newLyrics; + } + lyrics = newLyrics; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + return lyrics; + } + + public static void toggleFavorite(@NonNull final Context context, @NonNull final Song song) { + if (isFavorite(context, song)) { + PlaylistsUtil + .removeFromPlaylist(context, song, getFavoritesPlaylist(context).blockingFirst().id); + } else { + PlaylistsUtil + .addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).blockingFirst().id, + false); + } + } + + public static boolean isFavoritePlaylist(@NonNull final Context context, + @NonNull final Playlist playlist) { + return playlist.name != null && playlist.name.equals(context.getString(R.string.favorites)); + } + + private static Observable getFavoritesPlaylist(@NonNull final Context context) { + return PlaylistLoader.getPlaylist(context, context.getString(R.string.favorites)); + } + + private static Observable getOrCreateFavoritesPlaylist(@NonNull final Context context) { + return PlaylistLoader.getPlaylist(context, + PlaylistsUtil.createPlaylist(context, context.getString(R.string.favorites))); + } + + public static boolean isFavorite(@NonNull final Context context, @NonNull final Song song) { + /*return Observable.create(e -> getFavoritesPlaylist(context).subscribe(playlist1 -> { + boolean isBoolean = PlaylistsUtil.doPlaylistContains(context, playlist1.id, song.id); + e.onNext(isBoolean); + e.onComplete(); + }));*/ + + //getFavoritesPlaylist(context).blockingFirst().id.subscribe(MusicUtil::setPlaylist); + //return PlaylistsUtil.doPlaylistContains(context, getFavoritesPlaylist(context).blockingFirst().id, song.id); + return PlaylistsUtil + .doPlaylistContains(context, getFavoritesPlaylist(context).blockingFirst().id, song.id); + } + + public static boolean isArtistNameUnknown(@Nullable String artistName) { + if (TextUtils.isEmpty(artistName)) return false; + if (artistName.equals(Artist.UNKNOWN_ARTIST_DISPLAY_NAME)) return true; + artistName = artistName.trim().toLowerCase(); + return artistName.equals("unknown") || artistName.equals(""); + } + + @NonNull + public static String getSectionName(@Nullable String musicMediaTitle) { + if (TextUtils.isEmpty(musicMediaTitle)) { + return ""; + } + musicMediaTitle = musicMediaTitle.trim().toLowerCase(); + if (musicMediaTitle.startsWith("the ")) { + musicMediaTitle = musicMediaTitle.substring(4); + } else if (musicMediaTitle.startsWith("a ")) { + musicMediaTitle = musicMediaTitle.substring(2); + } + if (musicMediaTitle.isEmpty()) { + return ""; + } + return String.valueOf(musicMediaTitle.charAt(0)).toUpperCase(); + } + + public static Playlist getPlaylist() { + return playlist; + } + + public static void setPlaylist(Playlist playlist) { + MusicUtil.playlist = playlist; + } + + public static long getTotalDuration(@NonNull final Context context, @NonNull List songs) { + long duration = 0; + for (int i = 0; i < songs.size(); i++) { + duration += songs.get(i).duration; + } + return duration; + } + + @NonNull + public static String getYearString(int year) { + return year > 0 ? String.valueOf(year) : "-"; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java new file mode 100755 index 00000000..e86c0305 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java @@ -0,0 +1,120 @@ +package code.name.monkey.retromusic.util; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.media.audiofx.AudioEffect; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.util.Pair; +import android.widget.Toast; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.MusicPlayerRemote; +import code.name.monkey.retromusic.model.Genre; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.ui.activities.AboutActivity; +import code.name.monkey.retromusic.ui.activities.AlbumDetailsActivity; +import code.name.monkey.retromusic.ui.activities.ArtistDetailActivity; +import code.name.monkey.retromusic.ui.activities.EqualizerActivity; +import code.name.monkey.retromusic.ui.activities.GenreDetailsActivity; +import code.name.monkey.retromusic.ui.activities.LicenseActivity; +import code.name.monkey.retromusic.ui.activities.LyricsActivity; +import code.name.monkey.retromusic.ui.activities.MainActivity; +import code.name.monkey.retromusic.ui.activities.PlayingQueueActivity; +import code.name.monkey.retromusic.ui.activities.PlaylistDetailActivity; +import code.name.monkey.retromusic.ui.activities.ProVersionActivity; +import code.name.monkey.retromusic.ui.activities.SearchActivity; +import code.name.monkey.retromusic.ui.activities.SettingsActivity; +import code.name.monkey.retromusic.ui.activities.UserInfoActivity; + +import static code.name.monkey.retromusic.ui.activities.GenreDetailsActivity.EXTRA_GENRE_ID; + +public class NavigationUtil { + public static void goToAlbum(@NonNull Activity activity, int i, @Nullable Pair... sharedElements) { + Intent intent = new Intent(activity, AlbumDetailsActivity.class); + intent.putExtra(AlbumDetailsActivity.EXTRA_ALBUM_ID, i); + //noinspection unchecked + ActivityCompat.startActivity(activity, intent, + ActivityOptionsCompat.makeSceneTransitionAnimation(activity, sharedElements).toBundle()); + } + + public static void goToArtist(@NonNull Activity activity, int i, @Nullable Pair... sharedElements) { + Intent intent = new Intent(activity, ArtistDetailActivity.class); + intent.putExtra(ArtistDetailActivity.EXTRA_ARTIST_ID, i); + //noinspection unchecked + ActivityCompat.startActivity(activity, intent, null); + } + + public static void goToPlaylistNew(@NonNull Activity activity, Playlist playlist) { + Intent intent = new Intent(activity, PlaylistDetailActivity.class); + intent.putExtra(PlaylistDetailActivity.EXTRA_PLAYLIST, playlist); + ActivityCompat.startActivity(activity, intent, null); + } + + public static void openEqualizer(@NonNull final Activity activity) { + if (PreferenceUtil.getInstance(activity).getSelectedEqualizer().equals("system")) { + stockEqalizer(activity); + } else { + ActivityCompat.startActivity(activity, new Intent(activity, EqualizerActivity.class), null); + } + } + + private static void stockEqalizer(@NonNull Activity activity) { + final int sessionId = MusicPlayerRemote.getAudioSessionId(); + if (sessionId == AudioEffect.ERROR_BAD_VALUE) { + Toast.makeText(activity, activity.getResources().getString(R.string.no_audio_ID), Toast.LENGTH_LONG).show(); + } else { + try { + final Intent effects = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL); + effects.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId); + effects.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); + activity.startActivityForResult(effects, 0); + } catch (@NonNull final ActivityNotFoundException notFound) { + Toast.makeText(activity, activity.getResources().getString(R.string.no_equalizer), Toast.LENGTH_SHORT).show(); + } + } + } + + public static void goToPlayingQueue(@NonNull Activity activity) { + Intent intent = new Intent(activity, PlayingQueueActivity.class); + ActivityCompat.startActivity(activity, intent, null); + } + + public static void goToLyrics(@NonNull Activity activity) { + Intent intent = new Intent(activity, LyricsActivity.class); + ActivityCompat.startActivity(activity, intent, null); + } + + public static void goToGenre(@NonNull Activity activity, @NonNull Genre genre) { + Intent intent = new Intent(activity, GenreDetailsActivity.class); + intent.putExtra(EXTRA_GENRE_ID, genre); + ActivityCompat.startActivity(activity, intent, null); + } + + public static void goToProVersion(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, ProVersionActivity.class), null); + } + + public static void goToSettings(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, SettingsActivity.class), null); + } + + public static void goToAbout(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, AboutActivity.class), null); + } + + public static void goToUserInfo(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, UserInfoActivity.class), null); + } + + public static void goToOpenSource(@NonNull Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, LicenseActivity.class), null); + } + + public static void goToSearch(Activity activity) { + ActivityCompat.startActivity(activity, new Intent(activity, SearchActivity.class), null); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java new file mode 100644 index 00000000..cb82867e --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java @@ -0,0 +1,250 @@ +package code.name.monkey.retromusic.util; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.BaseColumns; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.helper.M3UWriter; +import code.name.monkey.retromusic.model.Playlist; +import code.name.monkey.retromusic.model.PlaylistSong; +import code.name.monkey.retromusic.model.Song; +import io.reactivex.Observable; + +import static android.provider.MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI; + + +public class PlaylistsUtil { + public static boolean doesPlaylistExist(@NonNull final Context context, final int playlistId) { + if (playlistId == -1) { + return false; + } + + Cursor cursor = context.getContentResolver().query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[]{}, null, null, null); + + if (cursor == null || cursor.getCount() == 0) { + return false; + } + + cursor.close(); + return true; + } + + public static int createPlaylist(@NonNull final Context context, @Nullable final String name) { + int id = -1; + if (name != null && name.length() > 0) { + try { + Cursor cursor = context.getContentResolver().query(EXTERNAL_CONTENT_URI, + new String[]{MediaStore.Audio.Playlists._ID}, MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[]{name}, null); + if (cursor == null || cursor.getCount() < 1) { + final ContentValues values = new ContentValues(1); + values.put(MediaStore.Audio.PlaylistsColumns.NAME, name); + final Uri uri = context.getContentResolver().insert( + EXTERNAL_CONTENT_URI, + values); + if (uri != null) { + // necessary because somehow the MediaStoreObserver is not notified when adding a playlist + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + Toast.makeText(context, context.getResources().getString( + R.string.created_playlist_x, name), Toast.LENGTH_SHORT).show(); + id = Integer.parseInt(uri.getLastPathSegment()); + } + } else { + if (cursor.moveToFirst()) { + id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID)); + } + } + if (cursor != null) { + cursor.close(); + } + } catch (SecurityException ignored) { + } + } + if (id == -1) { + Toast.makeText(context, context.getResources().getString( + R.string.could_not_create_playlist), Toast.LENGTH_SHORT).show(); + } + return id; + } + + public static void deletePlaylists(@NonNull final Context context, @NonNull final ArrayList playlists) { + final StringBuilder selection = new StringBuilder(); + selection.append(MediaStore.Audio.Playlists._ID + " IN ("); + for (int i = 0; i < playlists.size(); i++) { + selection.append(playlists.get(i).id); + if (i < playlists.size() - 1) { + selection.append(","); + } + } + selection.append(")"); + try { + context.getContentResolver().delete(EXTERNAL_CONTENT_URI, selection.toString(), null); + } catch (SecurityException ignored) { + } + } + + public static void addToPlaylist(@NonNull final Context context, final Song song, final int playlistId, final boolean showToastOnFinish) { + List helperList = new ArrayList<>(); + helperList.add(song); + addToPlaylist(context, helperList, playlistId, showToastOnFinish); + } + + public static void addToPlaylist(@NonNull final Context context, @NonNull final List songs, final int playlistId, final boolean showToastOnFinish) { + final int size = songs.size(); + final ContentResolver resolver = context.getContentResolver(); + final String[] projection = new String[]{ + "max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")", + }; + final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId); + Cursor cursor = null; + int base = 0; + + try { + try { + cursor = resolver.query(uri, projection, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + base = cursor.getInt(0) + 1; + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + int numInserted = 0; + for (int offSet = 0; offSet < size; offSet += 1000) + numInserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base)); + + if (showToastOnFinish) { + Toast.makeText(context, context.getResources().getString( + R.string.inserted_x_songs_into_playlist_x, numInserted, getNameForPlaylist(context, playlistId)), Toast.LENGTH_SHORT).show(); + } + } catch (SecurityException ignored) { + } + } + + @NonNull + public static ContentValues[] makeInsertItems(@NonNull final List songs, final int offset, int len, final int base) { + if (offset + len > songs.size()) { + len = songs.size() - offset; + } + + ContentValues[] contentValues = new ContentValues[len]; + + for (int i = 0; i < len; i++) { + contentValues[i] = new ContentValues(); + contentValues[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i); + contentValues[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, songs.get(offset + i).id); + } + return contentValues; + } + + public static void removeFromPlaylist(@NonNull final Context context, @NonNull final Song song, int playlistId) { + Uri uri = MediaStore.Audio.Playlists.Members.getContentUri( + "external", playlistId); + String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?"; + String[] selectionArgs = new String[]{String.valueOf(song.id)}; + + try { + context.getContentResolver().delete(uri, selection, selectionArgs); + } catch (SecurityException ignored) { + } + } + + public static void removeFromPlaylist(@NonNull final Context context, @NonNull final List songs) { + final int playlistId = songs.get(0).playlistId; + Uri uri = MediaStore.Audio.Playlists.Members.getContentUri( + "external", playlistId); + String selectionArgs[] = new String[songs.size()]; + for (int i = 0; i < selectionArgs.length; i++) { + selectionArgs[i] = String.valueOf(songs.get(i).idInPlayList); + } + String selection = MediaStore.Audio.Playlists.Members._ID + " in ("; + //noinspection unused + for (String selectionArg : selectionArgs) selection += "?, "; + selection = selection.substring(0, selection.length() - 2) + ")"; + + try { + context.getContentResolver().delete(uri, selection, selectionArgs); + } catch (SecurityException ignored) { + } + } + + public static boolean doPlaylistContains(@NonNull final Context context, final long playlistId, final int songId) { + if (playlistId != -1) { + try { + Cursor c = context.getContentResolver().query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[]{MediaStore.Audio.Playlists.Members.AUDIO_ID}, MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", new String[]{String.valueOf(songId)}, null); + int count = 0; + if (c != null) { + count = c.getCount(); + c.close(); + } + return count > 0; + } catch (SecurityException ignored) { + } + } + return false; + } + + public static boolean moveItem(@NonNull final Context context, int playlistId, int from, int to) { + return MediaStore.Audio.Playlists.Members.moveItem(context.getContentResolver(), + playlistId, from, to); + } + + public static void renamePlaylist(@NonNull final Context context, final long id, final String newName) { + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName); + try { + context.getContentResolver().update(EXTERNAL_CONTENT_URI, + contentValues, + MediaStore.Audio.Playlists._ID + "=?", + new String[]{String.valueOf(id)}); + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + } catch (SecurityException ignored) { + } + } + + public static String getNameForPlaylist(@NonNull final Context context, final long id) { + try { + Cursor cursor = context.getContentResolver().query( + EXTERNAL_CONTENT_URI, + new String[]{MediaStore.Audio.PlaylistsColumns.NAME}, + BaseColumns._ID + "=?", + new String[]{String.valueOf(id)}, + null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getString(0); + } + } finally { + cursor.close(); + } + } + } catch (SecurityException ignored) { + } + return ""; + } + + public static Observable savePlaylist(Context context, Playlist playlist) { + return M3UWriter.write(context, new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist); + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java new file mode 100644 index 00000000..3192aa36 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/PreferenceUtil.java @@ -0,0 +1,705 @@ +package code.name.monkey.retromusic.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.TypedArray; +import android.preference.PreferenceManager; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.StyleRes; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.ArrayList; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.RetroApplication; +import code.name.monkey.retromusic.helper.SortOrder; +import code.name.monkey.retromusic.model.CategoryInfo; +import code.name.monkey.retromusic.ui.fragments.NowPlayingScreen; +import code.name.monkey.retromusic.ui.fragments.mainactivity.folders.FoldersFragment; + +public final class PreferenceUtil { + + public static final String KEEP_SCREEN_ON = "keep_screen_on"; + public static final String NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"; + public static final String CAROUSEL_EFFECT = "carousel_effect"; + public static final String COLORED_NOTIFICATION = "colored_notification"; + public static final String CLASSIC_NOTIFICATION = "classic_notification"; + public static final String GAPLESS_PLAYBACK = "gapless_playback"; + public static final String ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen"; + public static final String BLURRED_ALBUM_ART = "blurred_album_art"; + public static final String TOGGLE_HEADSET = "toggle_headset"; + public static final String DOMINANT_COLOR = "dominant_color"; + public static final String GENERAL_THEME = "general_theme"; + public static final String CIRCULAR_ALBUM_ART = "circular_album_art"; + public static final String USER_NAME = "user_name"; + public static final String TOGGLE_FULL_SCREEN = "toggle_full_screen"; + public static final String TOGGLE_VOLUME = "toggle_volume"; + public static final String TOGGLE_TAB_TITLES = "toggle_tab_titles"; + public static final String ROUND_CORNERS = "corner_window"; + public static final String TOGGLE_GENRE = "toggle_genre"; + public static final String PROFILE_IMAGE_PATH = "profile_image_path"; + public static final String BANNER_IMAGE_PATH = "banner_image_path"; + public static final String ADAPTIVE_COLOR_APP = "adaptive_color_app"; + public static final String TOGGLE_SEPARATE_LINE = "toggle_separate_line"; + private static final String GENRE_SORT_ORDER = "genre_sort_order"; + private static final String ALBUM_GRID_STYLE = "album_grid_style"; + private static final String ARTIST_GRID_STYLE = "artist_grid_style"; + private static final String LIBRARY_CATEGORIES = "library_categories"; + private static final String LAST_PAGE = "last_start_page"; + private static final String LAST_MUSIC_CHOOSER = "last_music_chooser"; + private static final String DEFAULT_START_PAGE = "default_start_page"; + private static final String INITIALIZED_BLACKLIST = "initialized_blacklist"; + private static final String ARTIST_SORT_ORDER = "artist_sort_order"; + private static final String ARTIST_SONG_SORT_ORDER = "artist_song_sort_order"; + private static final String ARTIST_ALBUM_SORT_ORDER = "artist_album_sort_order"; + private static final String ALBUM_SORT_ORDER = "album_sort_order"; + private static final String ALBUM_SONG_SORT_ORDER = "album_song_sort_order"; + private static final String SONG_SORT_ORDER = "song_sort_order"; + private static final String ALBUM_GRID_SIZE = "album_grid_size"; + private static final String ALBUM_GRID_SIZE_LAND = "album_grid_size_land"; + private static final String SONG_GRID_SIZE = "song_grid_size"; + private static final String SONG_GRID_SIZE_LAND = "song_grid_size_land"; + private static final String ARTIST_GRID_SIZE = "artist_grid_size"; + private static final String ARTIST_GRID_SIZE_LAND = "artist_grid_size_land"; + private static final String ALBUM_COLORED_FOOTERS = "album_colored_footers"; + private static final String SONG_COLORED_FOOTERS = "song_colored_footers"; + private static final String ARTIST_COLORED_FOOTERS = "artist_colored_footers"; + private static final String ALBUM_ARTIST_COLORED_FOOTERS = "album_artist_colored_footers"; + private static final String COLORED_APP_SHORTCUTS = "colored_app_shortcuts"; + private static final String AUDIO_DUCKING = "audio_ducking"; + private static final String LAST_ADDED_CUTOFF = "last_added_interval"; + private static final String LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value"; + private static final String NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time"; + private static final String IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"; + private static final String LAST_CHANGELOG_VERSION = "last_changelog_version"; + private static final String INTRO_SHOWN = "intro_shown"; + private static final String AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"; + private static final String START_DIRECTORY = "start_directory"; + private static final String SYNCHRONIZED_LYRICS_SHOW = "synchronized_lyrics_show"; + private static final String LOCK_SCREEN = "lock_screen"; + private static final String ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"; + private static final String ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"; + private static final String LYRICS_OPTIONS = "lyrics_options"; + private static final String SAF_SDCARD_URI = "saf_sdcard_uri"; + private static final String DOCUMENT_TREE_URI = "document_tree_uri"; + private static final String CHOOSE_EQUALIZER = "choose_equalizer"; + private static final String TOGGLE_SHUFFLE = "toggle_shuffle"; + private static final String SONG_GRID_STYLE = "song_grid_style"; + private static final String TOGGLE_ANIMATIONS = "toggle_animations"; + private static final String TAG = "PreferenceUtil"; + private static PreferenceUtil sInstance; + private final SharedPreferences mPreferences; + + private PreferenceUtil(@NonNull final Context context) { + mPreferences = PreferenceManager.getDefaultSharedPreferences(context); + } + + public static PreferenceUtil getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new PreferenceUtil(context.getApplicationContext()); + } + return sInstance; + } + + @StyleRes + public static int getThemeResFromPrefValue(String themePrefValue) { + switch (themePrefValue) { + case "dark": + return R.style.Theme_RetroMusic; + case "color": + return R.style.Theme_RetroMusic_Color; + case "acolor": + return R.style.Theme_RetroMusic_Black; + case "black": + return R.style.Theme_RetroMusic_Black; + case "light": + default: + return R.style.Theme_RetroMusic_Light; + } + } + + public int getAlbumDetailsPageStyle() { + + TypedArray typedArray = RetroApplication.getInstance(). + getResources().obtainTypedArray(R.array.pref_album_detail_style_values); + int layout = typedArray.getResourceId(mPreferences.getInt("album_detail_style", 0), -1); + typedArray.recycle(); + return layout; + } + + public final String getArtistSortOrder() { + return mPreferences.getString(ARTIST_SORT_ORDER, SortOrder.ArtistSortOrder.ARTIST_A_Z); + } + + public void setArtistSortOrder(final String sortOrder) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(ARTIST_SORT_ORDER, sortOrder); + editor.apply(); + } + + public final String getArtistSongSortOrder() { + return mPreferences.getString(ARTIST_SONG_SORT_ORDER, SortOrder.ArtistSongSortOrder.SONG_A_Z); + } + + public final String getArtistAlbumSortOrder() { + return mPreferences + .getString(ARTIST_ALBUM_SORT_ORDER, SortOrder.ArtistAlbumSortOrder.ALBUM_YEAR); + } + + public final String getAlbumSortOrder() { + return mPreferences.getString(ALBUM_SORT_ORDER, SortOrder.AlbumSortOrder.ALBUM_A_Z); + } + + public void setAlbumSortOrder(final String sortOrder) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(ALBUM_SORT_ORDER, sortOrder); + editor.apply(); + } + + public final String getAlbumSongSortOrder() { + return mPreferences + .getString(ALBUM_SONG_SORT_ORDER, SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST); + } + + public final String getSongSortOrder() { + return mPreferences.getString(SONG_SORT_ORDER, SortOrder.SongSortOrder.SONG_A_Z); + } + + public void setSongSortOrder(final String sortOrder) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(SONG_SORT_ORDER, sortOrder); + editor.commit(); + } + + public final String getGenreSortOrder() { + return mPreferences.getString(GENRE_SORT_ORDER, SortOrder.GenreSortOrder.GENRE_A_Z); + } + + public boolean isScreenOnEnabled() { + return mPreferences.getBoolean(KEEP_SCREEN_ON, false); + } + + public void setInitializedBlacklist() { + final Editor editor = mPreferences.edit(); + editor.putBoolean(INITIALIZED_BLACKLIST, true); + editor.apply(); + } + + public final boolean initializedBlacklist() { + return mPreferences.getBoolean(INITIALIZED_BLACKLIST, false); + } + + + public boolean circularAlbumArt() { + return mPreferences.getBoolean(CIRCULAR_ALBUM_ART, false); + } + + public boolean carouselEffect() { + return mPreferences.getBoolean(CAROUSEL_EFFECT, false); + } + + public ArrayList getLibraryCategoryInfos() { + String data = mPreferences.getString(LIBRARY_CATEGORIES, null); + if (data != null) { + Gson gson = new Gson(); + Type collectionType = new TypeToken>() { + }.getType(); + + try { + return gson.fromJson(data, collectionType); + } catch (JsonSyntaxException e) { + e.printStackTrace(); + } + } + + return getDefaultLibraryCategoryInfos(); + } + + public void setLibraryCategoryInfos(ArrayList categories) { + Gson gson = new Gson(); + Type collectionType = new TypeToken>() { + }.getType(); + + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(LIBRARY_CATEGORIES, gson.toJson(categories, collectionType)); + editor.apply(); + } + + public ArrayList getDefaultLibraryCategoryInfos() { + ArrayList defaultCategoryInfos = new ArrayList<>(5); + defaultCategoryInfos.add(new CategoryInfo(CategoryInfo.Category.SONGS, true)); + defaultCategoryInfos.add(new CategoryInfo(CategoryInfo.Category.ALBUMS, true)); + defaultCategoryInfos.add(new CategoryInfo(CategoryInfo.Category.ARTISTS, true)); + defaultCategoryInfos.add(new CategoryInfo(CategoryInfo.Category.GENRES, true)); + defaultCategoryInfos.add(new CategoryInfo(CategoryInfo.Category.PLAYLISTS, true)); + return defaultCategoryInfos; + } + + public boolean isRoundCorners() { + return mPreferences.getBoolean(ROUND_CORNERS, false); + } + + public void registerOnSharedPreferenceChangedListener( + SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) { + mPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + } + + public void unregisterOnSharedPreferenceChangedListener( + SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener) { + mPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + } + + public final int getDefaultStartPage() { + return Integer.parseInt(mPreferences.getString(DEFAULT_START_PAGE, "-1")); + } + + + public final int getLastPage() { + return mPreferences.getInt(LAST_PAGE, 0); + } + + public void setLastPage(final int value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(LAST_PAGE, value); + editor.apply(); + } + + public final int getLastMusicChooser() { + return mPreferences.getInt(LAST_MUSIC_CHOOSER, 0); + } + + public void setLastMusicChooser(final int value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(LAST_MUSIC_CHOOSER, value); + editor.apply(); + } + + public final boolean coloredNotification() { + return mPreferences.getBoolean(COLORED_NOTIFICATION, true); + } + + public final boolean classicNotification() { + return mPreferences.getBoolean(CLASSIC_NOTIFICATION, false); + } + + public void setClassicNotification(final boolean value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(CLASSIC_NOTIFICATION, value); + editor.apply(); + } + + public final NowPlayingScreen getNowPlayingScreen() { + int id = mPreferences.getInt(NOW_PLAYING_SCREEN_ID, 0); + for (NowPlayingScreen nowPlayingScreen : NowPlayingScreen.values()) { + if (nowPlayingScreen.id == id) { + return nowPlayingScreen; + } + } + return NowPlayingScreen.NORMAL; + } + + @SuppressLint("CommitPrefEdits") + public void setNowPlayingScreen(NowPlayingScreen nowPlayingScreen) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(NOW_PLAYING_SCREEN_ID, nowPlayingScreen.id); + editor.apply(); + } + + public void setColoredAppShortcuts(final boolean value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(COLORED_APP_SHORTCUTS, value); + editor.apply(); + } + + public final boolean coloredAppShortcuts() { + return mPreferences.getBoolean(COLORED_APP_SHORTCUTS, true); + } + + public final boolean gaplessPlayback() { + return mPreferences.getBoolean(GAPLESS_PLAYBACK, false); + } + + public final boolean audioDucking() { + return mPreferences.getBoolean(AUDIO_DUCKING, true); + } + + public final boolean albumArtOnLockscreen() { + return mPreferences.getBoolean(ALBUM_ART_ON_LOCKSCREEN, true); + } + + public final boolean blurredAlbumArt() { + return mPreferences.getBoolean(BLURRED_ALBUM_ART, false); + } + + public final boolean ignoreMediaStoreArtwork() { + return mPreferences.getBoolean(IGNORE_MEDIA_STORE_ARTWORK, false); + } + + + public int getLastSleepTimerValue() { + return mPreferences.getInt(LAST_SLEEP_TIMER_VALUE, 30); + } + + public void setLastSleepTimerValue(final int value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(LAST_SLEEP_TIMER_VALUE, value); + editor.apply(); + } + + public long getNextSleepTimerElapsedRealTime() { + return mPreferences.getLong(NEXT_SLEEP_TIMER_ELAPSED_REALTIME, -1); + } + + public void setNextSleepTimerElapsedRealtime(final long value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putLong(NEXT_SLEEP_TIMER_ELAPSED_REALTIME, value); + editor.apply(); + } + + public final int getAlbumGridSize(Context context) { + return mPreferences + .getInt(ALBUM_GRID_SIZE, context.getResources().getInteger(R.integer.default_grid_columns)); + } + + public void setSongGridSize(final int gridSize) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(SONG_GRID_SIZE, gridSize); + editor.apply(); + } + + public final int getSongGridSize(Context context) { + return mPreferences + .getInt(SONG_GRID_SIZE, context.getResources().getInteger(R.integer.default_list_columns)); + } + + public void setArtistGridSize(final int gridSize) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(ARTIST_GRID_SIZE, gridSize); + editor.apply(); + } + + public final int getArtistGridSize(Context context) { + return mPreferences.getInt(ARTIST_GRID_SIZE, + context.getResources().getInteger(R.integer.default_list_artist_columns)); + } + + public void setAlbumGridSizeLand(final int gridSize) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(ALBUM_GRID_SIZE_LAND, gridSize); + editor.apply(); + } + + public final int getAlbumGridSizeLand(Context context) { + return mPreferences.getInt(ALBUM_GRID_SIZE_LAND, + context.getResources().getInteger(R.integer.default_grid_columns_land)); + } + + public void setSongGridSizeLand(final int gridSize) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(SONG_GRID_SIZE_LAND, gridSize); + editor.apply(); + } + + public final int getSongGridSizeLand(Context context) { + return mPreferences.getInt(SONG_GRID_SIZE_LAND, + context.getResources().getInteger(R.integer.default_list_columns_land)); + } + + public void setArtistGridSizeLand(final int gridSize) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(ARTIST_GRID_SIZE_LAND, gridSize); + editor.apply(); + } + + public final int getArtistGridSizeLand(Context context) { + return mPreferences.getInt(ARTIST_GRID_SIZE_LAND, + context.getResources().getInteger(R.integer.default_list_artist_columns_land)); + } + + public void setAlbumGridSize(final int gridSize) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putInt(ALBUM_GRID_SIZE, gridSize); + editor.apply(); + } + + public void setAlbumColoredFooters(final boolean value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(ALBUM_COLORED_FOOTERS, value); + editor.apply(); + } + + public final boolean albumColoredFooters() { + return mPreferences.getBoolean(ALBUM_COLORED_FOOTERS, false); + } + + public void setAlbumArtistColoredFooters(final boolean value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(ALBUM_ARTIST_COLORED_FOOTERS, value); + editor.apply(); + } + + public final boolean albumArtistColoredFooters() { + return mPreferences.getBoolean(ALBUM_ARTIST_COLORED_FOOTERS, true); + } + + public void setSongColoredFooters(final boolean value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(SONG_COLORED_FOOTERS, value); + editor.apply(); + } + + public final boolean songColoredFooters() { + return mPreferences.getBoolean(SONG_COLORED_FOOTERS, false); + } + + public void setArtistColoredFooters(final boolean value) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putBoolean(ARTIST_COLORED_FOOTERS, value); + editor.apply(); + } + + public final boolean artistColoredFooters() { + return mPreferences.getBoolean(ARTIST_COLORED_FOOTERS, true); + } + + public void setLastChangeLogVersion(int version) { + mPreferences.edit().putInt(LAST_CHANGELOG_VERSION, version).apply(); + } + + public final int getLastChangelogVersion() { + return mPreferences.getInt(LAST_CHANGELOG_VERSION, -1); + } + + @SuppressLint("CommitPrefEdits") + public void setIntroShown() { + // don't use apply here + mPreferences.edit().putBoolean(INTRO_SHOWN, true).commit(); + } + + public final File getStartDirectory() { + return new File(mPreferences + .getString(START_DIRECTORY, FoldersFragment.getDefaultStartDirectory().getPath())); + } + + public void setStartDirectory(File file) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(START_DIRECTORY, FileUtil.safeGetCanonicalPath(file)); + editor.apply(); + } + + public final boolean introShown() { + return mPreferences.getBoolean(INTRO_SHOWN, false); + } + + public final String autoDownloadImagesPolicy() { + return mPreferences.getString(AUTO_DOWNLOAD_IMAGES_POLICY, "only_wifi"); + } + + public final boolean synchronizedLyricsShow() { + return mPreferences.getBoolean(SYNCHRONIZED_LYRICS_SHOW, true); + } + + public int getGeneralTheme() { + return getThemeResFromPrefValue(mPreferences.getString(GENERAL_THEME, "light")); + } + + public void setGeneralTheme(String theme) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(GENERAL_THEME, theme); + editor.apply(); + } + + public long getLastAddedCutoff() { + final CalendarUtil calendarUtil = new CalendarUtil(); + long interval; + + switch (mPreferences.getString(LAST_ADDED_CUTOFF, "")) { + case "today": + interval = calendarUtil.getElapsedToday(); + break; + case "this_week": + interval = calendarUtil.getElapsedWeek(); + break; + case "past_three_months": + interval = calendarUtil.getElapsedMonths(3); + break; + case "this_year": + interval = calendarUtil.getElapsedYear(); + break; + case "this_month": + default: + interval = calendarUtil.getElapsedMonth(); + break; + } + + return (System.currentTimeMillis() - interval) / 1000; + } + + public boolean getAdaptiveColor() { + return mPreferences.getBoolean(ADAPTIVE_COLOR_APP, false); + } + + public boolean getLockScreen() { + return mPreferences.getBoolean(LOCK_SCREEN, false); + } + + public String getUserName() { + return mPreferences.getString(USER_NAME, "User"); + } + + public void setUserName(String name) { + mPreferences.edit().putString(USER_NAME, name).apply(); + } + + public boolean getFullScreenMode() { + return mPreferences.getBoolean(TOGGLE_FULL_SCREEN, false); + } + + public void setFullScreenMode(int newValue) { + mPreferences.edit().putInt(TOGGLE_FULL_SCREEN, newValue).apply(); + } + + public String lyricsOptions() { + return mPreferences.getString(LYRICS_OPTIONS, "offline"); + } + + public void saveProfileImage(String profileImagePath) { + mPreferences.edit().putString(PROFILE_IMAGE_PATH, profileImagePath) + .apply(); + + } + + public String getProfileImage() { + return mPreferences.getString(PROFILE_IMAGE_PATH, ""); + } + + public String getBannerImage() { + return mPreferences.getString(BANNER_IMAGE_PATH, ""); + } + + public void setBannerImagePath(String bannerImagePath) { + mPreferences.edit().putString(BANNER_IMAGE_PATH, bannerImagePath) + .apply(); + } + + public String getAlbumDetailSongSortOrder() { + return mPreferences + .getString(ALBUM_DETAIL_SONG_SORT_ORDER, SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST); + } + + public void setAlbumDetailSongSortOrder(String sortOrder) { + Editor edit = this.mPreferences.edit(); + edit.putString(ALBUM_DETAIL_SONG_SORT_ORDER, sortOrder); + edit.apply(); + } + + public String getArtistDetailSongSortOrder() { + return mPreferences + .getString(ARTIST_DETAIL_SONG_SORT_ORDER, SortOrder.ArtistSongSortOrder.SONG_A_Z); + } + + public void setArtistDetailSongSortOrder(String sortOrder) { + Editor edit = this.mPreferences.edit(); + edit.putString(ARTIST_DETAIL_SONG_SORT_ORDER, sortOrder); + edit.apply(); + } + + public boolean getVolumeToggle() { + return mPreferences.getBoolean(TOGGLE_VOLUME, false); + } + + public int getLyricsOptions() { + return mPreferences.getInt(LYRICS_OPTIONS, 1); + } + + public void setLyricsOptions(int i) { + mPreferences.edit().putInt(LYRICS_OPTIONS, i).apply(); + } + + public boolean getHeadsetPlugged() { + return mPreferences.getBoolean(TOGGLE_HEADSET, false); + } + + public boolean tabTitles() { + return mPreferences.getBoolean(TOGGLE_TAB_TITLES, true); + } + + public boolean isDominantColor() { + return mPreferences.getBoolean(DOMINANT_COLOR, false); + } + + public boolean isGenreShown() { + return mPreferences.getBoolean(TOGGLE_GENRE, false); + } + + public String getSelectedEqualizer() { + return mPreferences.getString(CHOOSE_EQUALIZER, "system"); + } + + public boolean isShuffleModeOn() { + return mPreferences.getBoolean(TOGGLE_SHUFFLE, false); + } + + public void resetCarouselEffect() { + mPreferences.edit().putBoolean(CAROUSEL_EFFECT, false).apply(); + } + + public void resetCircularAlbumArt() { + mPreferences.edit().putBoolean(CIRCULAR_ALBUM_ART, false).apply(); + } + + @LayoutRes + public int getAlbumGridStyle(Context context) { + int pos = Integer.parseInt(mPreferences.getString(ALBUM_GRID_STYLE, "0")); + TypedArray typedArray = context.getResources().obtainTypedArray(R.array.pref_grid_style_layout); + int layoutRes = typedArray.getResourceId(pos, -1); + typedArray.recycle(); + if (layoutRes == -1) { + return R.layout.item_card; + } + return layoutRes; + } + + public void setAlbumGridStyle(int type) { + mPreferences.edit().putInt(ALBUM_GRID_STYLE, type).apply(); + } + + public int getArtistGridStyle(Context context) { + int pos = Integer.parseInt(mPreferences.getString(ARTIST_GRID_STYLE, "0")); + TypedArray typedArray = context.getResources().obtainTypedArray(R.array.pref_grid_style_layout); + int layoutRes = typedArray.getResourceId(pos, -1); + typedArray.recycle(); + if (layoutRes == -1) { + return R.layout.item_card; + } + return layoutRes; + } + + public void setArtistGridStyle(int viewAs) { + mPreferences.edit().putInt(ARTIST_GRID_STYLE, viewAs).apply(); + } + + public boolean toggleSeparateLine() { + return mPreferences.getBoolean(TOGGLE_SEPARATE_LINE, false); + } + + public int getSongGridStyle() { + return mPreferences.getInt(SONG_GRID_STYLE, R.layout.item_list); + } + + public void setSongGridStyle(int viewAs) { + mPreferences.edit().putInt(SONG_GRID_STYLE, viewAs).apply(); + } + + public boolean enableAnimations() { + return mPreferences.getBoolean(TOGGLE_ANIMATIONS, false); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java new file mode 100644 index 00000000..64da365c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/RetroColorUtil.java @@ -0,0 +1,189 @@ +package code.name.monkey.retromusic.util; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.graphics.Palette; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import code.name.monkey.appthemehelper.util.ColorUtil; + +public class RetroColorUtil { + + @Nullable + public static Palette generatePalette(Bitmap bitmap) { + return bitmap == null ? null : Palette.from(bitmap).clearFilters().generate(); + } + + public static int getTextColor(@Nullable Palette palette) { + if (palette == null) { + return -1; + } + + int inverse = -1; + if (palette.getVibrantSwatch() != null) { + inverse = palette.getVibrantSwatch().getRgb(); + } else if (palette.getLightVibrantSwatch() != null) { + inverse = palette.getLightVibrantSwatch().getRgb(); + } else if (palette.getDarkVibrantSwatch() != null) { + inverse = palette.getDarkVibrantSwatch().getRgb(); + } + + int background = getSwatch(palette).getRgb(); + + if (inverse != -1) { + return ColorUtils.getReadableText(inverse, background, 150); + } + return ColorUtil.stripAlpha(getSwatch(palette).getTitleTextColor()); + } + + @NonNull + public static Palette.Swatch getSwatch(@Nullable Palette palette) { + if (palette == null) { + return new Palette.Swatch(Color.WHITE, 1); + } + return getBestPaletteSwatchFrom(palette.getSwatches()); + + } + + public static int getMatColor(Context context, String typeColor) { + int returnColor = Color.BLACK; + int arrayId = context.getResources().getIdentifier("md_" + typeColor, "array", + context.getApplicationContext().getPackageName()); + + if (arrayId != 0) { + TypedArray colors = context.getResources().obtainTypedArray(arrayId); + int index = (int) (Math.random() * colors.length()); + returnColor = colors.getColor(index, Color.BLACK); + colors.recycle(); + } + return returnColor; + } + + @ColorInt + public static int getColor(@Nullable Palette palette, int fallback) { + if (palette != null) { + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch().getRgb(); + } else if (palette.getDarkVibrantSwatch() != null) { + return palette.getDarkVibrantSwatch().getRgb(); + } else if (palette.getLightVibrantSwatch() != null) { + return palette.getLightVibrantSwatch().getRgb(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch().getRgb(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch().getRgb(); + } else if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch().getRgb(); + } else if (!palette.getSwatches().isEmpty()) { + return Collections.max(palette.getSwatches(), SwatchComparator.getInstance()).getRgb(); + } + } + return fallback; + } + + private static Palette.Swatch getTextSwatch(@Nullable Palette palette) { + if (palette == null) { + return new Palette.Swatch(Color.BLACK, 1); + } + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch(); + } else { + return new Palette.Swatch(Color.BLACK, 1); + } + } + + @ColorInt + public static int getBackgroundColor(@Nullable Palette palette) { + return getProperBackgroundSwatch(palette).getRgb(); + } + + private static Palette.Swatch getProperBackgroundSwatch(@Nullable Palette palette) { + if (palette == null) { + return new Palette.Swatch(Color.BLACK, 1); + } + if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch(); + } else { + return new Palette.Swatch(Color.BLACK, 1); + } + } + + private static Palette.Swatch getBestPaletteSwatchFrom(Palette palette) { + if (palette != null) { + if (palette.getVibrantSwatch() != null) { + return palette.getVibrantSwatch(); + } else if (palette.getMutedSwatch() != null) { + return palette.getMutedSwatch(); + } else if (palette.getDarkVibrantSwatch() != null) { + return palette.getDarkVibrantSwatch(); + } else if (palette.getDarkMutedSwatch() != null) { + return palette.getDarkMutedSwatch(); + } else if (palette.getLightVibrantSwatch() != null) { + return palette.getLightVibrantSwatch(); + } else if (palette.getLightMutedSwatch() != null) { + return palette.getLightMutedSwatch(); + } else if (!palette.getSwatches().isEmpty()) { + return getBestPaletteSwatchFrom(palette.getSwatches()); + } + } + return null; + } + + private static Palette.Swatch getBestPaletteSwatchFrom(List swatches) { + if (swatches == null) { + return null; + } + return Collections.max(swatches, (opt1, opt2) -> { + int a = opt1 == null ? 0 : opt1.getPopulation(); + int b = opt2 == null ? 0 : opt2.getPopulation(); + return a - b; + }); + } + + + public static int getDominantColor(Bitmap bitmap, int defaultFooterColor) { + List swatchesTemp = Palette.from(bitmap).generate().getSwatches(); + List swatches = new ArrayList(swatchesTemp); + Collections.sort(swatches, (swatch1, swatch2) -> swatch2.getPopulation() - swatch1.getPopulation()); + return swatches.size() > 0 ? swatches.get(0).getRgb() : defaultFooterColor; + } + + @ColorInt + public static int shiftBackgroundColorForLightText(@ColorInt int backgroundColor) { + while (ColorUtil.isColorLight(backgroundColor)) { + backgroundColor = ColorUtil.darkenColor(backgroundColor); + } + return backgroundColor; + } + + + private static class SwatchComparator implements Comparator { + + private static SwatchComparator sInstance; + + static SwatchComparator getInstance() { + if (sInstance == null) { + sInstance = new SwatchComparator(); + } + return sInstance; + } + + @Override + public int compare(Palette.Swatch lhs, Palette.Swatch rhs) { + return lhs.getPopulation() - rhs.getPopulation(); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java new file mode 100755 index 00000000..e66e5627 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java @@ -0,0 +1,314 @@ +package code.name.monkey.retromusic.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.Build; +import android.os.ResultReceiver; +import android.provider.BaseColumns; +import android.provider.MediaStore; +import android.support.annotation.ColorInt; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.graphics.drawable.VectorDrawableCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Collections; +import java.util.List; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.TintHelper; +import code.name.monkey.retromusic.RetroApplication; + +public class RetroUtil { + + private static final int[] TEMP_ARRAY = new int[1]; + + public static int calculateNoOfColumns(Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + float dpWidth = displayMetrics.widthPixels / displayMetrics.density; + return (int) (dpWidth / 180); + } + + public static Uri getAlbumArtUri(long paramInt) { + return ContentUris + .withAppendedId(Uri.parse("content://media/external/audio/albumart"), paramInt); + } + + public static String EncodeString(String string) { + return string.replace("%", "%25") + .replace(".", "%2E") + .replace("#", "%23") + .replace("$", "%24") + .replace("/", "%2F") + .replace("[", "%5B") + .replace("]", "%5D"); + } + + public static String DecodeString(String string) { + return string.replace("%25", "%") + .replace("%2E", ".") + .replace("%23", "#") + .replace("%24", "$") + .replace("%2F", "/") + .replace("%5B", "[") + .replace("%5D", "]"); + } + + public static boolean isTablet(@NonNull final Resources resources) { + return resources.getConfiguration().smallestScreenWidthDp >= 600; + } + + public static boolean isLandscape(@NonNull final Resources resources) { + return resources.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } + + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isRTL(@NonNull Context context) { + Configuration config = context.getResources().getConfiguration(); + return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } + + + @TargetApi(19) + public static void setStatusBarTranslucent(@NonNull Window window) { + window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + + public static void setAllowDrawUnderStatusBar(@NonNull Window window) { + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + + public static boolean isMarshMellow() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + } + + public static boolean isNougat() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + } + + public static boolean isOreo() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + + public static float getDistance(float x1, float y1, float x2, float y2) { + return (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + } + + public static float convertDpToPixel(float dp, Context context) { + Resources resources = context.getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); + } + + public static float convertPixelsToDp(float px, Context context) { + Resources resources = context.getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); + } + + + public static void openUrl(AppCompatActivity context, String str) { + Intent intent = new Intent("android.intent.action.VIEW"); + intent.setData(Uri.parse(str)); + intent.setFlags(268435456); + context.startActivity(intent); + } + + public static Point getScreenSize(@NonNull Context c) { + Display display = null; + if (c.getSystemService(Context.WINDOW_SERVICE) != null) { + display = ((WindowManager) c.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + } + Point size = new Point(); + if (display != null) { + display.getSize(size); + } + return size; + } + + + public static void hideSoftKeyboard(@Nullable Activity activity) { + if (activity != null) { + View currentFocus = activity.getCurrentFocus(); + if (currentFocus != null) { + InputMethodManager inputMethodManager = (InputMethodManager) activity + .getSystemService(Activity.INPUT_METHOD_SERVICE); + if (inputMethodManager != null) { + inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0); + } + } + } + } + + public static void showIme(@NonNull View view) { + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService + (Context.INPUT_METHOD_SERVICE); + // the public methods don't seem to work for me, so… reflection. + try { + Method showSoftInputUnchecked = InputMethodManager.class.getMethod( + "showSoftInputUnchecked", int.class, ResultReceiver.class); + showSoftInputUnchecked.setAccessible(true); + showSoftInputUnchecked.invoke(imm, 0, null); + } catch (Exception e) { + // ho hum + } + } + + + public static Drawable getVectorDrawable(@NonNull Resources res, @DrawableRes int resId, + @Nullable Resources.Theme theme) { + if (Build.VERSION.SDK_INT >= 21) { + return res.getDrawable(resId, theme); + } + return VectorDrawableCompat.create(res, resId, theme); + } + + public static Drawable getTintedVectorDrawable(@NonNull Context context, @DrawableRes int id, + @ColorInt int color) { + return TintHelper + .createTintedDrawable(getVectorDrawable(context.getResources(), id, context.getTheme()), + color); + } + + public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int id, + @ColorInt int color) { + return TintHelper.createTintedDrawable(ContextCompat.getDrawable(context, id), color); + } + + public static Drawable getTintedDrawable(@DrawableRes int id) { + return TintHelper + .createTintedDrawable(ContextCompat.getDrawable(RetroApplication.getInstance(), id), + ThemeStore.accentColor(RetroApplication.getInstance())); + } + + public static Bitmap createBitmap(Drawable drawable, float sizeMultiplier) { + Bitmap bitmap = Bitmap.createBitmap((int) (drawable.getIntrinsicWidth() * sizeMultiplier), + (int) (drawable.getIntrinsicHeight() * sizeMultiplier), Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + drawable.setBounds(0, 0, c.getWidth(), c.getHeight()); + drawable.draw(c); + return bitmap; + } + + public static Drawable getTintedVectorDrawable(@NonNull Resources res, @DrawableRes int resId, + @Nullable Resources.Theme theme, @ColorInt int color) { + return TintHelper.createTintedDrawable(getVectorDrawable(res, resId, theme), color); + } + + public static boolean isAllowedToDownloadMetadata(final Context context) { + switch (PreferenceUtil.getInstance(context).autoDownloadImagesPolicy()) { + case "always": + return true; + case "only_wifi": + final ConnectivityManager connectivityManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); + return netInfo != null && netInfo.getType() == ConnectivityManager.TYPE_WIFI && netInfo + .isConnectedOrConnecting(); + case "never": + default: + return false; + } + } + + public static String getIPAddress(boolean useIPv4) { + try { + List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface intf : interfaces) { + List addrs = Collections.list(intf.getInetAddresses()); + for (InetAddress addr : addrs) { + if (!addr.isLoopbackAddress()) { + String sAddr = addr.getHostAddress(); + //boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr); + boolean isIPv4 = sAddr.indexOf(':') < 0; + + if (useIPv4) { + if (isIPv4) + return sAddr; + } else { + if (!isIPv4) { + int delim = sAddr.indexOf('%'); // drop ip6 zone suffix + return delim < 0 ? sAddr.toUpperCase() : sAddr.substring(0, delim).toUpperCase(); + } + } + } + } + } + } catch (Exception ex) { + } + return ""; + } + + public static Uri getSongUri(Context context, long id) { + final String[] projection = new String[]{ + BaseColumns._ID, MediaStore.MediaColumns.DATA, MediaStore.Audio.AudioColumns.ALBUM_ID + }; + final StringBuilder selection = new StringBuilder(); + selection.append(BaseColumns._ID + " IN ("); + selection.append(id); + selection.append(")"); + final Cursor c = context.getContentResolver().query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), + null, null); + + if (c == null) { + return null; + } + c.moveToFirst(); + + + try { + + Uri uri = Uri.parse(c.getString(1)); + c.close(); + + return uri; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static void statusBarHeight(View statusBar) { + statusBar.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(statusBar.getContext()))); + } + + 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; + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java b/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java new file mode 100644 index 00000000..52870404 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/SwipeAndDragHelper.java @@ -0,0 +1,55 @@ +package code.name.monkey.retromusic.util; + +import android.graphics.Canvas; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +public class SwipeAndDragHelper extends ItemTouchHelper.Callback { + + private ActionCompletionContract contract; + + public SwipeAndDragHelper(ActionCompletionContract contract) { + this.contract = contract; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + return makeMovementFlags(dragFlags, 0); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + contract.onViewMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public void onChildDraw(Canvas c, + RecyclerView recyclerView, + RecyclerView.ViewHolder viewHolder, + float dX, + float dY, + int actionState, + boolean isCurrentlyActive) { + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + float alpha = 1 - (Math.abs(dX) / recyclerView.getWidth()); + viewHolder.itemView.setAlpha(alpha); + } + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + + public interface ActionCompletionContract { + void onViewMoved(int oldPosition, int newPosition); + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/TempUtils.java b/app/src/main/java/code/name/monkey/retromusic/util/TempUtils.java new file mode 100644 index 00000000..9d2b86aa --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/TempUtils.java @@ -0,0 +1,68 @@ +package code.name.monkey.retromusic.util; + +/** + * @author Hemanth S (h4h13). + */ +public class TempUtils { + + // Enums + public static final int TEMPO_STROLL = 0; + public static final int TEMPO_WALK = 1; + public static final int TEMPO_LIGHT_JOG = 2; + public static final int TEMPO_JOG = 3; + public static final int TEMPO_RUN = 4; + public static final int TEMPO_SPRINT = 5; + public static final int TEMPO_UNKNOWN = 6; + + // take BPM as an int + public static int getTempoFromBPM(int bpm) { + + // STROLL less than 60 + if (bpm < 60) { + return TEMPO_STROLL; + } + + // WALK between 60 and 70, or between 120 and 140 + else if (bpm < 70 || bpm >= 120 && bpm < 140) { + return TEMPO_WALK; + } + + // LIGHT_JOG between 70 and 80, or between 140 and 160 + else if (bpm < 80 || bpm >= 140 && bpm < 160) { + return TEMPO_LIGHT_JOG; + } + + // JOG between 80 and 90, or between 160 and 180 + else if (bpm < 90 || bpm >= 160 && bpm < 180) { + return TEMPO_JOG; + } + + // RUN between 90 and 100, or between 180 and 200 + else if (bpm < 100 || bpm >= 180 && bpm < 200) { + return TEMPO_RUN; + } + + // SPRINT between 100 and 120 + else if (bpm < 120) { + return TEMPO_SPRINT; + } + + // UNKNOWN + else { + return TEMPO_UNKNOWN; + } + } + + // take BPM as a string + public static int getTempoFromBPM(String bpm) { + // cast to an int from string + try { + // convert the string to an int + return getTempoFromBPM(Integer.parseInt(bpm.trim())); + } catch (NumberFormatException nfe) { + + // + return TEMPO_UNKNOWN; + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.java new file mode 100644 index 00000000..c20887cb --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/ViewUtil.java @@ -0,0 +1,58 @@ +package code.name.monkey.retromusic.util; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.support.v4.view.ViewCompat; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import code.name.monkey.appthemehelper.util.ColorUtil; +import code.name.monkey.appthemehelper.util.MaterialValueHelper; +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView; + +public class ViewUtil { + + public final static int RETRO_MUSIC_ANIM_TIME = 1000; + + public static void setStatusBarHeight(final Context context, View statusBar) { + ViewGroup.LayoutParams lp = statusBar.getLayoutParams(); + lp.height = getStatusBarHeight(context); + statusBar.requestLayout(); + } + + public static int getStatusBarHeight(final 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 boolean hitTest(View v, int x, int y) { + final int tx = (int) (ViewCompat.getTranslationX(v) + 0.5f); + final int ty = (int) (ViewCompat.getTranslationY(v) + 0.5f); + final int left = v.getLeft() + tx; + final int right = v.getRight() + tx; + final int top = v.getTop() + ty; + final int bottom = v.getBottom() + ty; + + return (x >= left) && (x <= right) && (y >= top) && (y <= bottom); + } + + public static void setUpFastScrollRecyclerViewColor(Context context, + FastScrollRecyclerView recyclerView, int accentColor) { + recyclerView.setPopupBgColor(accentColor); + recyclerView.setPopupTextColor( + MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(accentColor))); + recyclerView.setThumbColor(accentColor); + recyclerView.setTrackColor(Color.TRANSPARENT); + //recyclerView.setTrackColor(ColorUtil.withAlpha(ATHUtil.resolveColor(context, R.attr.colorControlNormal), 0.12f)); + } + + public static float convertDpToPixel(float dp, Resources resources) { + DisplayMetrics metrics = resources.getDisplayMetrics(); + return dp * metrics.density; + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/ImageGradientColorizer.java b/app/src/main/java/code/name/monkey/retromusic/util/color/ImageGradientColorizer.java new file mode 100644 index 00000000..87b88332 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/ImageGradientColorizer.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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 code.name.monkey.retromusic.util.color; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Shader; +import android.graphics.drawable.Drawable; + +/** + * A utility class to colorize bitmaps with a color gradient and a special blending mode + */ +public class ImageGradientColorizer { + + public Bitmap colorize(Drawable drawable, int backgroundColor, boolean isRtl) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + int size = Math.min(width, height); + int widthInset = (width - size) / 2; + int heightInset = (height - size) / 2; + drawable = drawable.mutate(); + drawable.setBounds(-widthInset, -heightInset, width - widthInset, height - heightInset); + Bitmap newBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + // Values to calculate the luminance of a color + float lr = 0.2126f; + float lg = 0.7152f; + float lb = 0.0722f; + // Extract the red, green, blue components of the color extraction color in + // float and int form + int tri = Color.red(backgroundColor); + int tgi = Color.green(backgroundColor); + int tbi = Color.blue(backgroundColor); + float tr = tri / 255f; + float tg = tgi / 255f; + float tb = tbi / 255f; + // Calculate the luminance of the color extraction color + float cLum = (tr * lr + tg * lg + tb * lb) * 255; + ColorMatrix m = new ColorMatrix(new float[]{ + lr, lg, lb, 0, tri - cLum, + lr, lg, lb, 0, tgi - cLum, + lr, lg, lb, 0, tbi - cLum, + 0, 0, 0, 1, 0, + }); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + LinearGradient linearGradient = new LinearGradient(0, 0, size, 0, + new int[]{0, Color.argb(0.5f, 1, 1, 1), Color.BLACK}, + new float[]{0.0f, 0.4f, 1.0f}, Shader.TileMode.CLAMP); + paint.setShader(linearGradient); + Bitmap fadeIn = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas fadeInCanvas = new Canvas(fadeIn); + drawable.clearColorFilter(); + drawable.draw(fadeInCanvas); + if (isRtl) { + // Let's flip the gradient + fadeInCanvas.translate(size, 0); + fadeInCanvas.scale(-1, 1); + } + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); + fadeInCanvas.drawPaint(paint); + Paint coloredPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + coloredPaint.setColorFilter(new ColorMatrixColorFilter(m)); + coloredPaint.setAlpha((int) (0.5f * 255)); + canvas.drawBitmap(fadeIn, 0, 0, coloredPaint); + linearGradient = new LinearGradient(0, 0, size, 0, + new int[]{0, Color.argb(0.5f, 1, 1, 1), Color.BLACK}, + new float[]{0.0f, 0.6f, 1.0f}, Shader.TileMode.CLAMP); + paint.setShader(linearGradient); + fadeInCanvas.drawPaint(paint); + canvas.drawBitmap(fadeIn, 0, 0, null); + return newBitmap; + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java b/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java new file mode 100644 index 00000000..af4d34dd --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/MediaNotificationProcessor.java @@ -0,0 +1,506 @@ +package code.name.monkey.retromusic.util.color; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.VisibleForTesting; +import android.support.v7.graphics.Palette; +import android.util.LayoutDirection; + +import code.name.monkey.appthemehelper.util.ColorUtil; + +import java.util.List; + +/** + * @author Hemanth S (h4h13). + */ +public class MediaNotificationProcessor { + + /** + * The fraction below which we select the vibrant instead of the light/dark vibrant color + */ + private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f; + /** + * Minimum saturation that a muted color must have if there exists if deciding between two + * colors + */ + private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f; + /** + * Minimum fraction that any color must have to be picked up as a text color + */ + private static final double MINIMUM_IMAGE_FRACTION = 0.002; + /** + * The population fraction to select the dominant color as the text color over a the colored + * ones. + */ + private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f; + /** + * The population fraction to select a white or black color as the background over a color. + */ + private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; + private static final float BLACK_MAX_LIGHTNESS = 0.08f; + private static final float WHITE_MIN_LIGHTNESS = 0.90f; + private static final int RESIZE_BITMAP_AREA = 150 * 150; + private final ImageGradientColorizer mColorizer; + private final Context mContext; + private float[] mFilteredBackgroundHsl = null; + private Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl); + /** + * The context of the notification. This is the app context of the package posting the + * notification. + */ + private final Context mPackageContext; + private boolean mIsLowPriority; + private onColorThing onColorThing; + + public MediaNotificationProcessor(Context context, Context packageContext, onColorThing thing) { + this(context, packageContext, new ImageGradientColorizer()); + onColorThing = thing; + } + + @VisibleForTesting + MediaNotificationProcessor(Context context, Context packageContext, + ImageGradientColorizer colorizer) { + mContext = context; + mPackageContext = packageContext; + mColorizer = colorizer; + } + + /** + * Processes a builder of a media notification and calculates the appropriate colors that should + * be used. + * + * @param notification the notification that is being processed + * @param builder the recovered builder for the notification. this will be modified + */ + public int processNotification(Bitmap image) { + Bitmap bitmap; + Drawable drawable = new BitmapDrawable(mPackageContext.getResources(), image); + + int backgroundColor = 0; + + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + int area = width * height; + if (area > RESIZE_BITMAP_AREA) { + double factor = Math.sqrt((float) RESIZE_BITMAP_AREA / area); + width = (int) (factor * width); + height = (int) (factor * height); + } + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, width, height); + drawable.draw(canvas); + // for the background we only take the left side of the image to ensure + // a smooth transition + Palette.Builder paletteBuilder = Palette.from(bitmap) + .setRegion(0, 0, bitmap.getWidth() / 2, bitmap.getHeight()) + .clearFilters() // we want all colors, red / white / black ones too! + .resizeBitmapArea(RESIZE_BITMAP_AREA); + Palette palette = paletteBuilder.generate(); + backgroundColor = findBackgroundColorAndFilter(palette); + // we want most of the full region again, slightly shifted to the right + + + float textColorStartWidthFraction = 0.4f; + paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0, + bitmap.getWidth(), + bitmap.getHeight()); + if (mFilteredBackgroundHsl != null) { + paletteBuilder.addFilter((rgb, hsl) -> { + // at least 10 degrees hue difference + float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]); + return diff > 10 && diff < 350; + }); + } + paletteBuilder.addFilter(mBlackWhiteFilter); + palette = paletteBuilder.generate(); + int foregroundColor = selectForegroundColor(backgroundColor, palette); + + onColorThing.bothColor(backgroundColor, foregroundColor); + return backgroundColor; + } + + private int selectForegroundColor(int backgroundColor, Palette palette) { + if (ColorUtil.isColorLight(backgroundColor)) { + return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(), + palette.getVibrantSwatch(), + palette.getDarkMutedSwatch(), + palette.getMutedSwatch(), + palette.getDominantSwatch(), + Color.BLACK); + } else { + return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(), + palette.getVibrantSwatch(), + palette.getLightMutedSwatch(), + palette.getMutedSwatch(), + palette.getDominantSwatch(), + Color.WHITE); + } + } + + private int selectForegroundColorForSwatches(Palette.Swatch moreVibrant, + Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch, + Palette.Swatch dominantSwatch, int fallbackColor) { + Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant); + if (coloredCandidate == null) { + coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch); + } + if (coloredCandidate != null) { + if (dominantSwatch == coloredCandidate) { + return coloredCandidate.getRgb(); + } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation() + < POPULATION_FRACTION_FOR_DOMINANT + && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) { + return dominantSwatch.getRgb(); + } else { + return coloredCandidate.getRgb(); + } + } else if (hasEnoughPopulation(dominantSwatch)) { + return dominantSwatch.getRgb(); + } else { + return fallbackColor; + } + } + + private Palette.Swatch selectMutedCandidate(Palette.Swatch first, + Palette.Swatch second) { + boolean firstValid = hasEnoughPopulation(first); + boolean secondValid = hasEnoughPopulation(second); + if (firstValid && secondValid) { + float firstSaturation = first.getHsl()[1]; + float secondSaturation = second.getHsl()[1]; + float populationFraction = first.getPopulation() / (float) second.getPopulation(); + if (firstSaturation * populationFraction > secondSaturation) { + return first; + } else { + return second; + } + } else if (firstValid) { + return first; + } else if (secondValid) { + return second; + } + return null; + } + + private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) { + boolean firstValid = hasEnoughPopulation(first); + boolean secondValid = hasEnoughPopulation(second); + if (firstValid && secondValid) { + int firstPopulation = first.getPopulation(); + int secondPopulation = second.getPopulation(); + if (firstPopulation / (float) secondPopulation + < POPULATION_FRACTION_FOR_MORE_VIBRANT) { + return second; + } else { + return first; + } + } else if (firstValid) { + return first; + } else if (secondValid) { + return second; + } + return null; + } + + private boolean hasEnoughPopulation(Palette.Swatch swatch) { + // We want a fraction that is at least 1% of the image + return swatch != null + && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION); + } + + private int findBackgroundColorAndFilter(Palette palette) { + // by default we use the dominant palette + Palette.Swatch dominantSwatch = palette.getDominantSwatch(); + if (dominantSwatch == null) { + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return Color.WHITE; + } + if (!isWhiteOrBlack(dominantSwatch.getHsl())) { + mFilteredBackgroundHsl = dominantSwatch.getHsl(); + return dominantSwatch.getRgb(); + } + // Oh well, we selected black or white. Lets look at the second color! + List swatches = palette.getSwatches(); + float highestNonWhitePopulation = -1; + Palette.Swatch second = null; + for (Palette.Swatch swatch : swatches) { + if (swatch != dominantSwatch + && swatch.getPopulation() > highestNonWhitePopulation + && !isWhiteOrBlack(swatch.getHsl())) { + second = swatch; + highestNonWhitePopulation = swatch.getPopulation(); + } + } + if (second == null) { + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return dominantSwatch.getRgb(); + } + if (dominantSwatch.getPopulation() / highestNonWhitePopulation + > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { + // The dominant swatch is very dominant, lets take it! + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return dominantSwatch.getRgb(); + } else { + mFilteredBackgroundHsl = second.getHsl(); + return second.getRgb(); + } + } + + private boolean isWhiteOrBlack(float[] hsl) { + return isBlack(hsl) || isWhite(hsl); + } + + /** + * @return true if the color represents a color which is close to black. + */ + private boolean isBlack(float[] hslColor) { + return hslColor[2] <= BLACK_MAX_LIGHTNESS; + } + + /** + * @return true if the color represents a color which is close to white. + */ + private boolean isWhite(float[] hslColor) { + return hslColor[2] >= WHITE_MIN_LIGHTNESS; + } + + public void setIsLowPriority(boolean isLowPriority) { + mIsLowPriority = isLowPriority; + } + + public interface onColorThing { + void bothColor(int i, int i2); + } + + /** + * The fraction below which we select the vibrant instead of the light/dark vibrant color + *//* + private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f; + *//** + * Minimum saturation that a muted color must have if there exists if deciding between two colors + *//* + private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f; + *//** + * Minimum fraction that any color must have to be picked up as a text color + *//* + private static final double MINIMUM_IMAGE_FRACTION = 0.002; + *//** + * The population fraction to select the dominant color as the text color over a the colored + * ones. + *//* + private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f; + *//** + * The population fraction to select a white or black color as the background over a color. + *//* + private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; + + private static final float BLACK_MAX_LIGHTNESS = 0.08f; + private static final float WHITE_MIN_LIGHTNESS = 0.90f; + private static final int RESIZE_BITMAP_AREA = 150 * 150; + private static float[] mFilteredBackgroundHsl = null; + private final ImageGradientColorizer mColorizer; + private final Context mContext; + *//** + * The context of the notification. This is the app context of the package posting the + * notification. + *//* + private final Context mPackageContext; + private static Palette.Filter mBlackWhiteFilter = (rgb, hsl) -> !isWhiteOrBlack(hsl); + private boolean mIsLowPriority; + + public MediaNotificationProcessor(Context context, Context packageContext) { + this(context, packageContext, new ImageGradientColorizer()); + } + + @VisibleForTesting + MediaNotificationProcessor(Context context, Context packageContext, + ImageGradientColorizer colorizer) { + mContext = context; + mPackageContext = packageContext; + mColorizer = colorizer; + } + + @Nullable + public static Palette.Builder generatePalette(Bitmap bitmap) { + return bitmap == null ? null : Palette.from(bitmap).clearFilters().resizeBitmapArea(RESIZE_BITMAP_AREA); + } + + public static int getBackgroundColor(Palette.Builder builder) { + return findBackgroundColorAndFilter(builder.generate()); + } + + public static int getTextColor(Palette.Builder builder) { + int backgroundColor = 0; + if (mFilteredBackgroundHsl != null) { + builder.addFilter((rgb, hsl) -> { + // at least 10 degrees hue difference + float diff = Math.abs(hsl[0] - mFilteredBackgroundHsl[0]); + return diff > 10 && diff < 350; + }); + } + builder.addFilter(mBlackWhiteFilter); + Palette palette = builder.generate(); + return selectForegroundColor(backgroundColor, palette); + } + + private static int selectForegroundColor(int backgroundColor, Palette palette) { + if (ColorUtil.isColorLight(backgroundColor)) { + return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(), + palette.getVibrantSwatch(), + palette.getDarkMutedSwatch(), + palette.getMutedSwatch(), + palette.getDominantSwatch(), + Color.BLACK); + } else { + return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(), + palette.getVibrantSwatch(), + palette.getLightMutedSwatch(), + palette.getMutedSwatch(), + palette.getDominantSwatch(), + Color.WHITE); + } + } + + private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant, + Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch, + Palette.Swatch dominantSwatch, int fallbackColor) { + Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant); + if (coloredCandidate == null) { + coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch); + } + if (coloredCandidate != null) { + if (dominantSwatch == coloredCandidate) { + return coloredCandidate.getRgb(); + } else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation() + < POPULATION_FRACTION_FOR_DOMINANT + && dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) { + return dominantSwatch.getRgb(); + } else { + return coloredCandidate.getRgb(); + } + } else if (hasEnoughPopulation(dominantSwatch)) { + return dominantSwatch.getRgb(); + } else { + return fallbackColor; + } + } + + private static Palette.Swatch selectMutedCandidate(Palette.Swatch first, + Palette.Swatch second) { + boolean firstValid = hasEnoughPopulation(first); + boolean secondValid = hasEnoughPopulation(second); + if (firstValid && secondValid) { + float firstSaturation = first.getHsl()[1]; + float secondSaturation = second.getHsl()[1]; + float populationFraction = first.getPopulation() / (float) second.getPopulation(); + if (firstSaturation * populationFraction > secondSaturation) { + return first; + } else { + return second; + } + } else if (firstValid) { + return first; + } else if (secondValid) { + return second; + } + return null; + } + + private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first, + Palette.Swatch second) { + boolean firstValid = hasEnoughPopulation(first); + boolean secondValid = hasEnoughPopulation(second); + if (firstValid && secondValid) { + int firstPopulation = first.getPopulation(); + int secondPopulation = second.getPopulation(); + if (firstPopulation / (float) secondPopulation + < POPULATION_FRACTION_FOR_MORE_VIBRANT) { + return second; + } else { + return first; + } + } else if (firstValid) { + return first; + } else if (secondValid) { + return second; + } + return null; + } + + private static boolean hasEnoughPopulation(Palette.Swatch swatch) { + // We want a fraction that is at least 1% of the image + return swatch != null + && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION); + } + + public static int findBackgroundColorAndFilter(Palette palette) { + // by default we use the dominant palette + Palette.Swatch dominantSwatch = palette.getDominantSwatch(); + if (dominantSwatch == null) { + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return Color.WHITE; + } + if (!isWhiteOrBlack(dominantSwatch.getHsl())) { + mFilteredBackgroundHsl = dominantSwatch.getHsl(); + return dominantSwatch.getRgb(); + } + // Oh well, we selected black or white. Lets look at the second color! + List swatches = palette.getSwatches(); + float highestNonWhitePopulation = -1; + Palette.Swatch second = null; + for (Palette.Swatch swatch : swatches) { + if (swatch != dominantSwatch + && swatch.getPopulation() > highestNonWhitePopulation + && !isWhiteOrBlack(swatch.getHsl())) { + second = swatch; + highestNonWhitePopulation = swatch.getPopulation(); + } + } + if (second == null) { + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return dominantSwatch.getRgb(); + } + if (dominantSwatch.getPopulation() / highestNonWhitePopulation + > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { + // The dominant swatch is very dominant, lets take it! + // We're not filtering on white or black + mFilteredBackgroundHsl = null; + return dominantSwatch.getRgb(); + } else { + mFilteredBackgroundHsl = second.getHsl(); + return second.getRgb(); + } + } + + private static boolean isWhiteOrBlack(float[] hsl) { + return isBlack(hsl) || isWhite(hsl); + } + + *//** + * @return true if the color represents a color which is close to black. + *//* + private static boolean isBlack(float[] hslColor) { + return hslColor[2] <= BLACK_MAX_LIGHTNESS; + } + + *//** + * @return true if the color represents a color which is close to white. + *//* + private static boolean isWhite(float[] hslColor) { + return hslColor[2] >= WHITE_MIN_LIGHTNESS; + } + + public void setIsLowPriority(boolean isLowPriority) { + mIsLowPriority = isLowPriority; + }*/ +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java new file mode 100644 index 00000000..f85d97fd --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/color/NotificationColorUtil.java @@ -0,0 +1,134 @@ +package code.name.monkey.retromusic.util.color; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.TextAppearanceSpan; +import android.util.Log; +import android.util.Pair; +import code.name.monkey.retromusic.util.ImageUtil; +import java.util.Arrays; +import java.util.WeakHashMap; + +/** + * Helper class to process legacy (Holo) notifications to make them look like quantum + * notifications. + * + * @hide + */ +public class NotificationColorUtil { + + private static final String TAG = "NotificationColorUtil"; + private static final Object sLock = new Object(); + private static NotificationColorUtil sInstance; + + private final WeakHashMap> mGrayscaleBitmapCache = + new WeakHashMap>(); + + public static NotificationColorUtil getInstance() { + synchronized (sLock) { + if (sInstance == null) { + sInstance = new NotificationColorUtil(); + } + return sInstance; + } + } + + /** + * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect gray". + * + * @param bitmap The bitmap to test. + * @return Whether the bitmap is grayscale. + */ + public boolean isGrayscale(Bitmap bitmap) { + synchronized (sLock) { + Pair cached = mGrayscaleBitmapCache.get(bitmap); + if (cached != null) { + if (cached.second == bitmap.getGenerationId()) { + return cached.first; + } + } + } + boolean result; + int generationId; + + result = ImageUtil.isGrayscale(bitmap); + // generationId and the check whether the Bitmap is grayscale can't be read atomically + // here. However, since the thread is in the process of posting the notification, we can + // assume that it doesn't modify the bitmap while we are checking the pixels. + generationId = bitmap.getGenerationId(); + + synchronized (sLock) { + mGrayscaleBitmapCache.put(bitmap, Pair.create(result, generationId)); + } + return result; + } + + /** + * Checks whether a drawable is grayscale. Grayscale here means "very close to a perfect gray". + * + * @param d The drawable to test. + * @return Whether the drawable is grayscale. + */ + public boolean isGrayscale(Drawable d) { + if (d == null) { + return false; + } else if (d instanceof BitmapDrawable) { + BitmapDrawable bd = (BitmapDrawable) d; + return bd.getBitmap() != null && isGrayscale(bd.getBitmap()); + } else if (d instanceof AnimationDrawable) { + AnimationDrawable ad = (AnimationDrawable) d; + int count = ad.getNumberOfFrames(); + return count > 0 && isGrayscale(ad.getFrame(0)); + } else if (d instanceof VectorDrawable) { + // We just assume you're doing the right thing if using vectors + return true; + } else { + return false; + } + } + + /** + * Checks whether a drawable with a resoure id is grayscale. Grayscale here means "very close to a + * perfect gray". + * + * @param context The context to load the drawable from. + * @return Whether the drawable is grayscale. + */ + public boolean isGrayscale(Context context, int drawableResId) { + if (drawableResId != 0) { + try { + return isGrayscale(context.getDrawable(drawableResId)); + } catch (Resources.NotFoundException ex) { + Log.e(TAG, "Drawable not found: " + drawableResId); + return false; + } + } else { + return false; + } + } + + /** + * Inverts all the grayscale colors set by {@link android.text.style.TextAppearanceSpan}s on the + * text. + * + * @param charSequence The text to process. + * @return The color inverted text. + */ + + + private int processColor(int color) { + return Color.argb(Color.alpha(color), + 255 - Color.red(color), + 255 - Color.green(color), + 255 - Color.blue(color)); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/util/schedulers/BaseSchedulerProvider.java b/app/src/main/java/code/name/monkey/retromusic/util/schedulers/BaseSchedulerProvider.java new file mode 100644 index 00000000..6e610579 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/schedulers/BaseSchedulerProvider.java @@ -0,0 +1,20 @@ +package code.name.monkey.retromusic.util.schedulers; + +import android.support.annotation.NonNull; + +import io.reactivex.Scheduler; + +/** + * Created by hemanths on 12/08/17. + */ + +public interface BaseSchedulerProvider { + @NonNull + Scheduler computation(); + + @NonNull + Scheduler io(); + + @NonNull + Scheduler ui(); +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/schedulers/ImmediateScheduler.java b/app/src/main/java/code/name/monkey/retromusic/util/schedulers/ImmediateScheduler.java new file mode 100644 index 00000000..d5284bab --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/schedulers/ImmediateScheduler.java @@ -0,0 +1,30 @@ +package code.name.monkey.retromusic.util.schedulers; + +import android.support.annotation.NonNull; + +import io.reactivex.Scheduler; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by hemanths on 12/08/17. + */ + +public class ImmediateScheduler implements BaseSchedulerProvider { + @NonNull + @Override + public Scheduler computation() { + return Schedulers.trampoline(); + } + + @NonNull + @Override + public Scheduler io() { + return Schedulers.trampoline(); + } + + @NonNull + @Override + public Scheduler ui() { + return Schedulers.trampoline(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/util/schedulers/SchedulerProvider.java b/app/src/main/java/code/name/monkey/retromusic/util/schedulers/SchedulerProvider.java new file mode 100644 index 00000000..4ca45b14 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/util/schedulers/SchedulerProvider.java @@ -0,0 +1,44 @@ +package code.name.monkey.retromusic.util.schedulers; + +import android.support.annotation.NonNull; + +import io.reactivex.Scheduler; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by hemanths on 12/08/17. + */ + +public class SchedulerProvider implements BaseSchedulerProvider { + @NonNull + private static SchedulerProvider INSTANCE ; + + public SchedulerProvider() { + } + + public static synchronized SchedulerProvider getInstance() { + if (INSTANCE == null) { + INSTANCE = new SchedulerProvider(); + } + return INSTANCE; + } + + @Override + @NonNull + public Scheduler computation() { + return Schedulers.computation(); + } + + @Override + @NonNull + public Scheduler io() { + return Schedulers.io(); + } + + @Override + @NonNull + public Scheduler ui() { + return AndroidSchedulers.mainThread(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/AutofitRecyclerView.java b/app/src/main/java/code/name/monkey/retromusic/views/AutofitRecyclerView.java new file mode 100644 index 00000000..a513a46f --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/AutofitRecyclerView.java @@ -0,0 +1,51 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + +/** + * @author Hemanth S (h4h13). + */ +public class AutofitRecyclerView extends RecyclerView { + private GridLayoutManager manager; + private int columnWidth = -1; //default value + + public AutofitRecyclerView(Context context) { + super(context); + init(context, null); + } + + public AutofitRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public AutofitRecyclerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + if (attrs != null) { + int[] attrsArray = {android.R.attr.columnWidth}; + TypedArray array = context.obtainStyledAttributes(attrs, attrsArray); + columnWidth = array.getDimensionPixelSize(0, -1); + array.recycle(); + } + + manager = new GridLayoutManager(getContext(), 1, GridLayoutManager.HORIZONTAL,false); + setLayoutManager(manager); + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + super.onMeasure(widthSpec, heightSpec); + if (columnWidth > 0) { + int spanCount = Math.max(1, getMeasuredWidth() / columnWidth); + manager.setSpanCount(spanCount); + } + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BottomNavigationViewEx.java b/app/src/main/java/code/name/monkey/retromusic/views/BottomNavigationViewEx.java new file mode 100644 index 00000000..ce2e4421 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/BottomNavigationViewEx.java @@ -0,0 +1,1050 @@ +package code.name.monkey.retromusic.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.internal.BottomNavigationItemView; +import android.support.design.internal.BottomNavigationMenuView; +import android.support.design.widget.BottomNavigationView; +import android.support.transition.Transition; +import android.support.transition.TransitionSet; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.SparseIntArray; +import android.util.TypedValue; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; + +import code.name.monkey.appthemehelper.ThemeStore; +import code.name.monkey.appthemehelper.util.ATHUtil; + +/** + * Created by yu on 2016/11/10. + */ +@SuppressLint("RestrictedApi") +public class BottomNavigationViewEx extends BottomNavigationView { + // used for animation + private int mShiftAmount; + private float mScaleUpFactor; + private float mScaleDownFactor; + private boolean animationRecord; + private float mLargeLabelSize; + private float mSmallLabelSize; + private boolean visibilityTextSizeRecord; + private boolean visibilityHeightRecord; + private int mItemHeight; + private boolean textVisibility = true; + // used for animation end + + // used for setupWithViewPager + private ViewPager mViewPager; + private MyOnNavigationItemSelectedListener mMyOnNavigationItemSelectedListener; + private BottomNavigationViewExOnPageChangeListener mPageChangeListener; + private BottomNavigationMenuView mMenuView; + private BottomNavigationItemView[] mButtons; + // used for setupWithViewPager end + + public BottomNavigationViewEx(Context context) { + super(context); + tintColor( ); + } + + public BottomNavigationViewEx(Context context, AttributeSet attrs) { + super(context, attrs); + tintColor( ); + } + + public BottomNavigationViewEx(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + tintColor( ); + } + + /** + * get text height by font size + * + * @param fontSize + * @return + */ + private static int getFontHeight(float fontSize) { + Paint paint = new Paint(); + paint.setTextSize(fontSize); + Paint.FontMetrics fm = paint.getFontMetrics(); + return (int) Math.ceil(fm.descent - fm.top) + 2; + } + + /** + * dp to px + * + * @param context + * @param dpValue dp + * @return px + */ + public static int dp2px(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + private static void setItemIconColors(@NonNull BottomNavigationView view, @ColorInt int normalColor, @ColorInt int selectedColor) { + ColorStateList iconSl = new ColorStateList(new int[][]{{-16842912}, {16842912}}, new int[]{normalColor, selectedColor}); + view.setItemIconTintList(iconSl); + } + + private static void setItemTextColors(@NonNull BottomNavigationView view, @ColorInt int normalColor, @ColorInt int selectedColor) { + ColorStateList textSl = new ColorStateList(new int[][]{{-16842912}, {16842912}}, new int[]{normalColor, selectedColor}); + view.setItemTextColor(textSl); + } + + public void setIconAndTextColor(int i) { + tintColor(ATHUtil.resolveColor(getContext(), android.R.attr.textColorSecondary), i); + } + + private void tintColor() { + int color = ATHUtil.resolveColor(getContext(), android.R.attr.textColorSecondary); + int accentColor = ThemeStore.accentColor(getContext()); + tintColor(color, accentColor); + } + + private void tintColor(int color, int accentColor) { + setItemIconColors(this, color, accentColor); + setItemTextColors(this, color, accentColor); + } + + private void init() { + try { + addAnimationListener(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void addAnimationListener() { + /** + * 1. BottomNavigationMenuView mMenuView + * 2. private final BottomNavigationAnimationHelperBase mAnimationHelper; + * 3. private final TransitionSet mSet; + */ + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + Object mAnimationHelper = getField(mMenuView.getClass(), mMenuView, "mAnimationHelper"); + TransitionSet mSet = getField(mAnimationHelper.getClass(), mAnimationHelper, "mSet"); + mSet.addListener(new Transition.TransitionListener() { + @Override + public void onTransitionStart(@NonNull Transition transition) { + } + + @Override + public void onTransitionEnd(@NonNull Transition transition) { + refreshTextViewVisibility(); + } + + @Override + public void onTransitionCancel(@NonNull Transition transition) { + refreshTextViewVisibility(); + } + + @Override + public void onTransitionPause(@NonNull Transition transition) { + } + + @Override + public void onTransitionResume(@NonNull Transition transition) { + } + }); + } + + private void refreshTextViewVisibility() { + if (!textVisibility) + return; + // 1. get mMenuView + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get mButtons + BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); + + int currentItem = getCurrentItem(); + + // 3. get field mShiftingMode and TextView in mButtons + for (BottomNavigationItemView button : mButtons) { + TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); + TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); + + if (mLargeLabel != null) { + mLargeLabel.clearAnimation(); + } + if (mSmallLabel != null) { + mSmallLabel.clearAnimation(); + } + + // mShiftingMode + boolean mShiftingMode = getField(button.getClass(), button, "mShiftingMode"); + boolean selected = button.getItemPosition() == currentItem; + if (mShiftingMode) { + if (selected) { + mLargeLabel.setVisibility(VISIBLE); + } else { + mLargeLabel.setVisibility(INVISIBLE); + } + mSmallLabel.setVisibility(INVISIBLE); + } else { + if (selected) { + mLargeLabel.setVisibility(VISIBLE); + mSmallLabel.setVisibility(INVISIBLE); + } else { + mLargeLabel.setVisibility(INVISIBLE); + mSmallLabel.setVisibility(VISIBLE); + } + } + } + } + + /** + * change the visibility of icon + * + * @param visibility + */ + public void setIconVisibility(boolean visibility) { + /* + 1. get field in this class + private final BottomNavigationMenuView mMenuView; + + 2. get field in mButtons + private BottomNavigationItemView[] mButtons; + + 3. get mIcon in mButtons + private ImageView mIcon + + 4. set mIcon visibility gone + + 5. change mItemHeight to only text size in mMenuView + */ + // 1. get mMenuView + final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get mButtons + BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); + // 3. get mIcon in mButtons + for (BottomNavigationItemView button : mButtons) { + ImageView mIcon = getField(button.getClass(), button, "mIcon"); + // 4. set mIcon visibility gone + mIcon.setVisibility(visibility ? View.VISIBLE : View.INVISIBLE); + } + + // 5. change mItemHeight to only text size in mMenuView + if (!visibility) { + // if not record mItemHeight + if (!visibilityHeightRecord) { + visibilityHeightRecord = true; + mItemHeight = getItemHeight(); + } + + // change mItemHeight + BottomNavigationItemView button = mButtons[0]; + if (null != button) { + final ImageView mIcon = getField(button.getClass(), button, "mIcon"); +// System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight()); + if (null != mIcon) { + mIcon.post(new Runnable() { + @Override + public void run() { +// System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight()); + setItemHeight(mItemHeight - mIcon.getMeasuredHeight()); + } + }); + } + } + } else { + // if not record the mItemHeight, we need do nothing. + if (!visibilityHeightRecord) + return; + + // restore it + setItemHeight(mItemHeight); + } + + mMenuView.updateMenuView(); + } + + /** + * change the visibility of text + * + * @param visibility + */ + public void setTextVisibility(boolean visibility) { + this.textVisibility = visibility; + /* + 1. get field in this class + private final BottomNavigationMenuView mMenuView; + + 2. get field in mButtons + private BottomNavigationItemView[] mButtons; + + 3. set text size in mButtons + private final TextView mLargeLabel + private final TextView mSmallLabel + + 4. change mItemHeight to only icon size in mMenuView + */ + // 1. get mMenuView + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get mButtons + BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); + + // 3. change field mShiftingMode value in mButtons + for (BottomNavigationItemView button : mButtons) { + TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); + TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); + + if (!visibility) { + // if not record the font size, record it + if (!visibilityTextSizeRecord && !animationRecord) { + visibilityTextSizeRecord = true; + mLargeLabelSize = mLargeLabel.getTextSize(); + mSmallLabelSize = mSmallLabel.getTextSize(); + } + + // if not visitable, set font size to 0 + mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0); + mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0); + + } else { + // if not record the font size, we need do nothing. + if (!visibilityTextSizeRecord) + break; + + // restore it + mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize); + mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize); + } + } + + // 4 change mItemHeight to only icon size in mMenuView + if (!visibility) { + // if not record mItemHeight + if (!visibilityHeightRecord) { + visibilityHeightRecord = true; + mItemHeight = getItemHeight(); + } + + // change mItemHeight to only icon size in mMenuView + // private final int mItemHeight; + + // change mItemHeight +// System.out.println("mLargeLabel.getMeasuredHeight():" + getFontHeight(mSmallLabelSize)); + setItemHeight(mItemHeight - getFontHeight(mSmallLabelSize)); + + } else { + // if not record the mItemHeight, we need do nothing. + if (!visibilityHeightRecord) + return; + // restore mItemHeight + setItemHeight(mItemHeight); + } + + mMenuView.updateMenuView(); + } + + /** + * enable or disable click item animation(text scale and icon move animation in no item shifting mode) + * + * @param enable It means the text won't scale and icon won't move when active it in no item shifting mode if false. + */ + public void enableAnimation(boolean enable) { + /* + 1. get field in this class + private final BottomNavigationMenuView mMenuView; + + 2. get field in mButtons + private BottomNavigationItemView[] mButtons; + + 3. chang mShiftAmount to 0 in mButtons + private final int mShiftAmount + + change mScaleUpFactor and mScaleDownFactor to 1f in mButtons + private final float mScaleUpFactor + private final float mScaleDownFactor + + 4. change label font size in mButtons + private final TextView mLargeLabel + private final TextView mSmallLabel + */ + + // 1. get mMenuView + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get mButtons + BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); + // 3. change field mShiftingMode value in mButtons + for (BottomNavigationItemView button : mButtons) { + TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); + TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); + + // if disable animation, need animationRecord the source value + if (!enable) { + if (!animationRecord) { + animationRecord = true; + mShiftAmount = getField(button.getClass(), button, "mShiftAmount"); + mScaleUpFactor = getField(button.getClass(), button, "mScaleUpFactor"); + mScaleDownFactor = getField(button.getClass(), button, "mScaleDownFactor"); + + mLargeLabelSize = mLargeLabel.getTextSize(); + mSmallLabelSize = mSmallLabel.getTextSize(); + +// System.out.println("mShiftAmount:" + mShiftAmount + " mScaleUpFactor:" +// + mScaleUpFactor + " mScaleDownFactor:" + mScaleDownFactor +// + " mLargeLabel:" + mLargeLabelSize + " mSmallLabel:" + mSmallLabelSize); + } + // disable + setField(button.getClass(), button, "mShiftAmount", 0); + setField(button.getClass(), button, "mScaleUpFactor", 1); + setField(button.getClass(), button, "mScaleDownFactor", 1); + + // let the mLargeLabel font size equal to mSmallLabel + mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize); + + // debug start +// mLargeLabelSize = mLargeLabel.getTextSize(); +// System.out.println("mLargeLabel:" + mLargeLabelSize); + // debug end + + } else { + // haven't change the value. It means it was the first call this method. So nothing need to do. + if (!animationRecord) + return; + // enable animation + setField(button.getClass(), button, "mShiftAmount", mShiftAmount); + setField(button.getClass(), button, "mScaleUpFactor", mScaleUpFactor); + setField(button.getClass(), button, "mScaleDownFactor", mScaleDownFactor); + // restore + mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize); + } + } + mMenuView.updateMenuView(); + } + + /** + * enable the shifting mode for navigation + * + * @param enable It will has a shift animation if true. Otherwise all items are the same width. + */ + public void enableShiftingMode(boolean enable) { + /* + 1. get field in this class + private final BottomNavigationMenuView mMenuView; + + 2. change field mShiftingMode value in mMenuView + private boolean mShiftingMode = true; + */ + // 1. get mMenuView + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. change field mShiftingMode value in mMenuView + setField(mMenuView.getClass(), mMenuView, "mShiftingMode", enable); + + mMenuView.updateMenuView(); + } + + /** + * enable the shifting mode for each item + * + * @param enable It will has a shift animation for item if true. Otherwise the item text always be shown. + */ + public void enableItemShiftingMode(boolean enable) { + /* + 1. get field in this class + private final BottomNavigationMenuView mMenuView; + + 2. get field in this mMenuView + private BottomNavigationItemView[] mButtons; + + 3. change field mShiftingMode value in mButtons + private boolean mShiftingMode = true; + */ + // 1. get mMenuView + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get mButtons + BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); + // 3. change field mShiftingMode value in mButtons + for (BottomNavigationItemView button : mButtons) { + setField(button.getClass(), button, "mShiftingMode", enable); + } + mMenuView.updateMenuView(); + } + + /** + * get the current checked item position + * + * @return index of item, start from 0. + */ + public int getCurrentItem() { + /* + 1. get field in this class + private final BottomNavigationMenuView mMenuView; + + 2. get field in mMenuView + private BottomNavigationItemView[] mButtons; + + 3. get menu and traverse it to get the checked one + */ + + // 1. get mMenuView +// BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get mButtons + BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); + // 3. get menu and traverse it to get the checked one + Menu menu = getMenu(); + for (int i = 0; i < mButtons.length; i++) { + if (menu.getItem(i).isChecked()) { + return i; + } + } + return 0; + } + + /** + * set the current checked item + * + * @param item start from 0. + */ + public void setCurrentItem(int item) { + // check bounds + if (item < 0 || item >= getMaxItemCount()) { + throw new ArrayIndexOutOfBoundsException("item is out of bounds, we expected 0 - " + + (getMaxItemCount() - 1) + ". Actually " + item); + } + + /* + 1. get field in this class + private final BottomNavigationMenuView mMenuView; + + 2. get field in mMenuView + private BottomNavigationItemView[] mButtons; + private final OnClickListener mOnClickListener; + + 3. call mOnClickListener.onClick(); + */ + // 1. get mMenuView + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get mButtons + BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); + // get mOnClickListener + View.OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "mOnClickListener"); + +// System.out.println("mMenuView:" + mMenuView + " mButtons:" + mButtons + " mOnClickListener" + mOnClickListener); + // 3. call mOnClickListener.onClick(); + mOnClickListener.onClick(mButtons[item]); + + } + + /** + * get menu item position in menu + * + * @param item + * @return position if success, -1 otherwise + */ + public int getMenuItemPosition(MenuItem item) { + // get item id + int itemId = item.getItemId(); + // get meunu + Menu menu = getMenu(); + int size = menu.size(); + for (int i = 0; i < size; i++) { + if (menu.getItem(i).getItemId() == itemId) { + return i; + } + } + return -1; + } + + /** + * get OnNavigationItemSelectedListener + * + * @return + */ + public OnNavigationItemSelectedListener getOnNavigationItemSelectedListener() { + // private OnNavigationItemSelectedListener mListener; + OnNavigationItemSelectedListener mListener = getField(BottomNavigationView.class, this, "mSelectedListener"); + return mListener; + } + + @Override + public void setOnNavigationItemSelectedListener(@Nullable OnNavigationItemSelectedListener listener) { + // if not set up with view pager, the same with father + if (null == mMyOnNavigationItemSelectedListener) { + super.setOnNavigationItemSelectedListener(listener); + return; + } + + mMyOnNavigationItemSelectedListener.setOnNavigationItemSelectedListener(listener); + } + + /** + * get private mMenuView + * + * @return + */ + private BottomNavigationMenuView getBottomNavigationMenuView() { + if (null == mMenuView) + mMenuView = getField(BottomNavigationView.class, this, "mMenuView"); + return mMenuView; + } + + /** + * get private mButtons in mMenuView + * + * @return + */ + public BottomNavigationItemView[] getBottomNavigationItemViews() { + if (null != mButtons) + return mButtons; + /* + * 1 private final BottomNavigationMenuView mMenuView; + * 2 private BottomNavigationItemView[] mButtons; + */ + BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + mButtons = getField(mMenuView.getClass(), mMenuView, "mButtons"); + return mButtons; + } + + /** + * get private mButton in mMenuView at position + * + * @param position + * @return + */ + public BottomNavigationItemView getBottomNavigationItemView(int position) { + return getBottomNavigationItemViews()[position]; + } + + /** + * get icon at position + * + * @param position + * @return + */ + public ImageView getIconAt(int position) { + /* + * 1 private final BottomNavigationMenuView mMenuView; + * 2 private BottomNavigationItemView[] mButtons; + * 3 private ImageView mIcon; + */ + BottomNavigationItemView mButtons = getBottomNavigationItemView(position); + ImageView mIcon = getField(BottomNavigationItemView.class, mButtons, "mIcon"); + return mIcon; + } + + /** + * get small label at position + * Each item has tow label, one is large, another is small. + * + * @param position + * @return + */ + public TextView getSmallLabelAt(int position) { + /* + * 1 private final BottomNavigationMenuView mMenuView; + * 2 private BottomNavigationItemView[] mButtons; + * 3 private final TextView mSmallLabel; + */ + BottomNavigationItemView mButtons = getBottomNavigationItemView(position); + TextView mSmallLabel = getField(BottomNavigationItemView.class, mButtons, "mSmallLabel"); + return mSmallLabel; + } + + /** + * get large label at position + * Each item has tow label, one is large, another is small. + * + * @param position + * @return + */ + public TextView getLargeLabelAt(int position) { + /* + * 1 private final BottomNavigationMenuView mMenuView; + * 2 private BottomNavigationItemView[] mButtons; + * 3 private final TextView mLargeLabel; + */ + BottomNavigationItemView mButtons = getBottomNavigationItemView(position); + TextView mLargeLabel = getField(BottomNavigationItemView.class, mButtons, "mLargeLabel"); + return mLargeLabel; + } + + /** + * return item count + * + * @return + */ + public int getItemCount() { + BottomNavigationItemView[] bottomNavigationItemViews = getBottomNavigationItemViews(); + if (null == bottomNavigationItemViews) + return 0; + return bottomNavigationItemViews.length; + } + + /** + * set all item small TextView size + * Each item has tow label, one is large, another is small. + * Small one will be shown when item state is normal + * Large one will be shown when item checked. + * + * @param sp + */ + public void setSmallTextSize(float sp) { + int count = getItemCount(); + for (int i = 0; i < count; i++) { + getSmallLabelAt(i).setTextSize(sp); + } + mMenuView.updateMenuView(); + } + + /** + * set all item large TextView size + * Each item has tow label, one is large, another is small. + * Small one will be shown when item state is normal. + * Large one will be shown when item checked. + * + * @param sp + */ + public void setLargeTextSize(float sp) { + int count = getItemCount(); + for (int i = 0; i < count; i++) { + getLargeLabelAt(i).setTextSize(sp); + } + mMenuView.updateMenuView(); + } + + /** + * set all item large and small TextView size + * Each item has tow label, one is large, another is small. + * Small one will be shown when item state is normal + * Large one will be shown when item checked. + * + * @param sp + */ + public void setTextSize(float sp) { + setLargeTextSize(sp); + setSmallTextSize(sp); + } + + /** + * set item ImageView size which at position + * + * @param position position start from 0 + * @param width in dp + * @param height in dp + */ + public void setIconSizeAt(int position, float width, float height) { + ImageView icon = getIconAt(position); + // update size + ViewGroup.LayoutParams layoutParams = icon.getLayoutParams(); + layoutParams.width = dp2px(getContext(), width); + layoutParams.height = dp2px(getContext(), height); + icon.setLayoutParams(layoutParams); + + mMenuView.updateMenuView(); + } + + /** + * set all item ImageView size + * + * @param width in dp + * @param height in dp + */ + public void setIconSize(float width, float height) { + int count = getItemCount(); + for (int i = 0; i < count; i++) { + setIconSizeAt(i, width, height); + } + } + + /** + * get menu item height + * + * @return in px + */ + public int getItemHeight() { + // 1. get mMenuView + final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. get private final int mItemHeight in mMenuView + return getField(mMenuView.getClass(), mMenuView, "mItemHeight"); + } + + /** + * set menu item height + * + * @param height in px + */ + public void setItemHeight(int height) { + // 1. get mMenuView + final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); + // 2. set private final int mItemHeight in mMenuView + setField(mMenuView.getClass(), mMenuView, "mItemHeight", height); + + mMenuView.updateMenuView(); + } + + /** + * set Typeface for all item TextView + * + * @attr ref android.R.styleable#TextView_typeface + * @attr ref android.R.styleable#TextView_textStyle + */ + public void setTypeface(Typeface typeface, int style) { + int count = getItemCount(); + for (int i = 0; i < count; i++) { + getLargeLabelAt(i).setTypeface(typeface, style); + getSmallLabelAt(i).setTypeface(typeface, style); + } + mMenuView.updateMenuView(); + } + + /** + * set Typeface for all item TextView + * + * @attr ref android.R.styleable#TextView_typeface + */ + public void setTypeface(Typeface typeface) { + int count = getItemCount(); + for (int i = 0; i < count; i++) { + getLargeLabelAt(i).setTypeface(typeface); + getSmallLabelAt(i).setTypeface(typeface); + } + mMenuView.updateMenuView(); + } + + /** + * get private filed in this specific object + * + * @param targetClass + * @param instance the filed owner + * @param fieldName + * @param + * @return field if success, null otherwise. + */ + private T getField(Class targetClass, Object instance, String fieldName) { + try { + Field field = targetClass.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(instance); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + /** + * change the field value + * + * @param targetClass + * @param instance the filed owner + * @param fieldName + * @param value + */ + private void setField(Class targetClass, Object instance, String fieldName, Object value) { + try { + Field field = targetClass.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(instance, value); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * This method will link the given ViewPager and this BottomNavigationViewEx together so that + * changes in one are automatically reflected in the other. This includes scroll state changes + * and clicks. + * + * @param viewPager + */ + public void setupWithViewPager(@Nullable final ViewPager viewPager) { + setupWithViewPager(viewPager, false); + } + + /** + * This method will link the given ViewPager and this BottomNavigationViewEx together so that + * changes in one are automatically reflected in the other. This includes scroll state changes + * and clicks. + * + * @param viewPager + * @param smoothScroll whether ViewPager changed with smooth scroll animation + */ + public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean smoothScroll) { + if (mViewPager != null) { + // If we've already been setup with a ViewPager, remove us from it + if (mPageChangeListener != null) { + mViewPager.removeOnPageChangeListener(mPageChangeListener); + } + } + + if (null == viewPager) { + mViewPager = null; + super.setOnNavigationItemSelectedListener(null); + return; + } + + mViewPager = viewPager; + + // Add our custom OnPageChangeListener to the ViewPager + if (mPageChangeListener == null) { + mPageChangeListener = new BottomNavigationViewExOnPageChangeListener(this); + } + viewPager.addOnPageChangeListener(mPageChangeListener); + + // Now we'll add a navigation item selected listener to set ViewPager's current item + OnNavigationItemSelectedListener listener = getOnNavigationItemSelectedListener(); + mMyOnNavigationItemSelectedListener = new MyOnNavigationItemSelectedListener(viewPager, this, smoothScroll, listener); + super.setOnNavigationItemSelectedListener(mMyOnNavigationItemSelectedListener); + } + + public void enableShiftingMode(int position, boolean enable) { + getBottomNavigationItemView(position).setShiftingMode(enable); + } + + public void setItemBackground(int position, int background) { + getBottomNavigationItemView(position).setItemBackground(background); + } + + public void setIconTintList(int position, ColorStateList tint) { + getBottomNavigationItemView(position).setIconTintList(tint); + } + + public void setTextTintList(int position, ColorStateList tint) { + getBottomNavigationItemView(position).setTextColor(tint); + } + + /** + * set margin top for all icons + * + * @param marginTop in px + */ + public void setIconsMarginTop(int marginTop) { + for (int i = 0; i < getItemCount(); i++) { + setIconMarginTop(i, marginTop); + } + } + + /** + * set margin top for icon + * + * @param position + * @param marginTop in px + */ + public void setIconMarginTop(int position, int marginTop) { + /* + 1. BottomNavigationItemView + 2. private final int mDefaultMargin; + */ + BottomNavigationItemView itemView = getBottomNavigationItemView(position); + setField(BottomNavigationItemView.class, itemView, "mDefaultMargin", marginTop); + mMenuView.updateMenuView(); + } + + + /** + * A {@link ViewPager.OnPageChangeListener} class which contains the + * necessary calls back to the provided {@link BottomNavigationViewEx} so that the tab position is + * kept in sync. + *

+ *

This class stores the provided BottomNavigationViewEx weakly, meaning that you can use + * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener) + * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and + * not cause a leak. + */ + private static class BottomNavigationViewExOnPageChangeListener implements ViewPager.OnPageChangeListener { + private final WeakReference mBnveRef; + + public BottomNavigationViewExOnPageChangeListener(BottomNavigationViewEx bnve) { + mBnveRef = new WeakReference<>(bnve); + } + + @Override + public void onPageScrollStateChanged(final int state) { + } + + @Override + public void onPageScrolled(final int position, final float positionOffset, + final int positionOffsetPixels) { + } + + @Override + public void onPageSelected(final int position) { + final BottomNavigationViewEx bnve = mBnveRef.get(); + if (null != bnve) + bnve.setCurrentItem(position); +// Log.d("onPageSelected", "--------- position " + position + " ------------"); + } + } + + /** + * Decorate OnNavigationItemSelectedListener for setupWithViewPager + */ + private static class MyOnNavigationItemSelectedListener implements OnNavigationItemSelectedListener { + private final WeakReference viewPagerRef; + private OnNavigationItemSelectedListener listener; + private boolean smoothScroll; + private SparseIntArray items;// used for change ViewPager selected item + private int previousPosition = -1; + + + MyOnNavigationItemSelectedListener(ViewPager viewPager, BottomNavigationViewEx bnve, boolean smoothScroll, OnNavigationItemSelectedListener listener) { + this.viewPagerRef = new WeakReference<>(viewPager); + this.listener = listener; + this.smoothScroll = smoothScroll; + + // create items + Menu menu = bnve.getMenu(); + int size = menu.size(); + items = new SparseIntArray(size); + for (int i = 0; i < size; i++) { + int itemId = menu.getItem(i).getItemId(); + items.put(itemId, i); + } + } + + public void setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) { + this.listener = listener; + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + int position = items.get(item.getItemId()); + // only set item when item changed + if (previousPosition == position) { + return true; + } + + // user listener + if (null != listener) { + boolean bool = listener.onNavigationItemSelected(item); + // if the selected is invalid, no need change the view pager + if (!bool) + return false; + } + + // change view pager + ViewPager viewPager = viewPagerRef.get(); + if (null == viewPager) + return false; + + viewPager.setCurrentItem(items.get(item.getItemId()), smoothScroll); + + // update previous position + previousPosition = position; + + return true; + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BottomSheetListView.java b/app/src/main/java/code/name/monkey/retromusic/views/BottomSheetListView.java new file mode 100644 index 00000000..b84e0a4d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/BottomSheetListView.java @@ -0,0 +1,41 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.AbsListView; +import android.widget.ListView; + +public class BottomSheetListView extends ListView { + public BottomSheetListView(Context context, AttributeSet p_attrs) { + super(context, p_attrs); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (canScrollVertically(this)) { + getParent().requestDisallowInterceptTouchEvent(true); + } + return super.onTouchEvent(ev); + } + + public boolean canScrollVertically(AbsListView view) { + boolean canScroll = false; + + if (view != null && view.getChildCount() > 0) { + boolean isOnTop = view.getFirstVisiblePosition() != 0 || view.getChildAt(0).getTop() != 0; + boolean isAllItemsVisible = isOnTop && view.getLastVisiblePosition() == view.getChildCount(); + + if (isOnTop || isAllItemsVisible) { + canScroll = true; + } + } + + return canScroll; + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java new file mode 100644 index 00000000..4f9019d7 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/BreadCrumbLayout.java @@ -0,0 +1,417 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import code.name.monkey.appthemehelper.ThemeStore; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import code.name.monkey.retromusic.R; + +/** + * @author Aidan Follestad (afollestad), modified for Phonograph by Karim Abou Zeid (kabouzeid) + */ +public class BreadCrumbLayout extends HorizontalScrollView implements View.OnClickListener { + + @ColorInt + private int contentColorActivated; + @ColorInt + private int contentColorDeactivated; + + public static class Crumb implements Parcelable { + + public Crumb(File file) { + this.file = file; + } + + private final File file; + private int scrollPos; + + public int getScrollPosition() { + return scrollPos; + } + + public void setScrollPosition(int scrollY) { + this.scrollPos = scrollY; + } + + public String getTitle() { + return file.getPath().equals("/") ? "root" : file.getName(); + } + + public File getFile() { + return file; + } + + @Override + public boolean equals(Object o) { + return (o instanceof Crumb) && ((Crumb) o).getFile() != null && + ((Crumb) o).getFile().equals(getFile()); + } + + @Override + public String toString() { + return "Crumb{" + + "file=" + file + + ", scrollPos=" + scrollPos + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeSerializable(this.file); + dest.writeInt(this.scrollPos); + } + + protected Crumb(Parcel in) { + this.file = (File) in.readSerializable(); + this.scrollPos = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Crumb createFromParcel(Parcel source) { + return new Crumb(source); + } + + @Override + public Crumb[] newArray(int size) { + return new Crumb[size]; + } + }; + } + + public interface SelectionCallback { + void onCrumbSelection(Crumb crumb, int index); + } + + public BreadCrumbLayout(Context context) { + super(context); + init(); + } + + public BreadCrumbLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public BreadCrumbLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + // Stores currently visible crumbs + private List mCrumbs; + // Used in setActiveOrAdd() between clearing crumbs and adding the new set, nullified afterwards + private List mOldCrumbs; + // Stores user's navigation history, like a fragment back stack + private List mHistory; + + private LinearLayout mChildFrame; + private int mActive; + private SelectionCallback mCallback; + + private void init() { + contentColorActivated = ThemeStore.textColorPrimary(getContext()); + contentColorDeactivated = ThemeStore.textColorSecondary(getContext()); + setMinimumHeight((int) getResources().getDimension(R.dimen.tab_height)); + setClipToPadding(false); + setHorizontalScrollBarEnabled(false); + mCrumbs = new ArrayList<>(); + mHistory = new ArrayList<>(); + mChildFrame = new LinearLayout(getContext()); + addView(mChildFrame, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } + + public void addHistory(Crumb crumb) { + mHistory.add(crumb); + } + + public Crumb lastHistory() { + if (mHistory.size() == 0) return null; + return mHistory.get(mHistory.size() - 1); + } + + public boolean popHistory() { + if (mHistory.size() == 0) return false; + mHistory.remove(mHistory.size() - 1); + return mHistory.size() != 0; + } + + public int historySize() { + return mHistory.size(); + } + + public void clearHistory() { + mHistory.clear(); + } + + public void reverseHistory() { + Collections.reverse(mHistory); + } + + public void addCrumb(@NonNull Crumb crumb, boolean refreshLayout) { + LinearLayout view = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.bread_crumb, this, false); + view.setTag(mCrumbs.size()); + view.setOnClickListener(this); + + ImageView iv = (ImageView) view.getChildAt(1); + if (Build.VERSION.SDK_INT >= 19 && iv.getDrawable() != null) { + iv.getDrawable().setAutoMirrored(true); + } + iv.setVisibility(View.GONE); + + mChildFrame.addView(view, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + mCrumbs.add(crumb); + if (refreshLayout) { + mActive = mCrumbs.size() - 1; + requestLayout(); + } + invalidateActivatedAll(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + //RTL works fine like this + View child = mChildFrame.getChildAt(mActive); + if (child != null) + smoothScrollTo(child.getLeft(), 0); + } + + public Crumb findCrumb(@NonNull File forDir) { + for (int i = 0; i < mCrumbs.size(); i++) { + if (mCrumbs.get(i).getFile().equals(forDir)) + return mCrumbs.get(i); + } + return null; + } + + public void clearCrumbs() { + try { + mOldCrumbs = new ArrayList<>(mCrumbs); + mCrumbs.clear(); + mChildFrame.removeAllViews(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + public Crumb getCrumb(int index) { + return mCrumbs.get(index); + } + + public void setCallback(SelectionCallback callback) { + mCallback = callback; + } + + private boolean setActive(Crumb newActive) { + mActive = mCrumbs.indexOf(newActive); + invalidateActivatedAll(); + boolean success = mActive > -1; + if (success) + requestLayout(); + return success; + } + + void invalidateActivatedAll() { + for (int i = 0; i < mCrumbs.size(); i++) { + Crumb crumb = mCrumbs.get(i); + invalidateActivated(mChildFrame.getChildAt(i), mActive == mCrumbs.indexOf(crumb), false, i < mCrumbs.size() - 1).setText(crumb.getTitle()); + } + } + + void removeCrumbAt(int index) { + mCrumbs.remove(index); + mChildFrame.removeViewAt(index); + } + + public boolean trim(String path, boolean dir) { + if (!dir) return false; + int index = -1; + for (int i = mCrumbs.size() - 1; i >= 0; i--) { + File fi = mCrumbs.get(i).getFile(); + if (fi.getPath().equals(path)) { + index = i; + break; + } + } + + boolean removedActive = index >= mActive; + if (index > -1) { + while (index <= mCrumbs.size() - 1) + removeCrumbAt(index); + if (mChildFrame.getChildCount() > 0) { + int lastIndex = mCrumbs.size() - 1; + invalidateActivated(mChildFrame.getChildAt(lastIndex), mActive == lastIndex, false, false); + } + } + return removedActive || mCrumbs.size() == 0; + } + + public boolean trim(File file) { + return trim(file.getPath(), file.isDirectory()); + } + + void updateIndices() { + for (int i = 0; i < mChildFrame.getChildCount(); i++) + mChildFrame.getChildAt(i).setTag(i); + } + + public void setActiveOrAdd(@NonNull Crumb crumb, boolean forceRecreate) { + if (forceRecreate || !setActive(crumb)) { + clearCrumbs(); + final List newPathSet = new ArrayList<>(); + + newPathSet.add(0, crumb.getFile()); + + File p = crumb.getFile(); + while ((p = p.getParentFile()) != null) { + newPathSet.add(0, p); + } + + for (int index = 0; index < newPathSet.size(); index++) { + final File fi = newPathSet.get(index); + crumb = new Crumb(fi); + + // Restore scroll positions saved before clearing + if (mOldCrumbs != null) { + for (Iterator iterator = mOldCrumbs.iterator(); iterator.hasNext(); ) { + Crumb old = iterator.next(); + if (old.equals(crumb)) { + crumb.setScrollPosition(old.getScrollPosition()); + iterator.remove(); // minimize number of linear passes by removing un-used crumbs from history + break; + } + } + } + + addCrumb(crumb, true); + } + + // History no longer needed + mOldCrumbs = null; + } + } + + public int size() { + return mCrumbs.size(); + } + + private TextView invalidateActivated(View view, final boolean isActive, final boolean noArrowIfAlone, final boolean allowArrowVisible) { + int contentColor = isActive ? contentColorActivated : contentColorDeactivated; + LinearLayout child = (LinearLayout) view; + TextView tv = (TextView) child.getChildAt(0); + tv.setTextColor(contentColor); + ImageView iv = (ImageView) child.getChildAt(1); + iv.setColorFilter(contentColor, PorterDuff.Mode.SRC_IN); + if (noArrowIfAlone && getChildCount() == 1) + iv.setVisibility(View.GONE); + else if (allowArrowVisible) + iv.setVisibility(View.VISIBLE); + else + iv.setVisibility(View.GONE); + return tv; + } + + public int getActiveIndex() { + return mActive; + } + + public void setActivatedContentColor(@ColorInt int contentColorActivated) { + this.contentColorActivated = contentColorActivated; + } + + public void setDeactivatedContentColor(@ColorInt int contentColorDeactivated) { + this.contentColorDeactivated = contentColorDeactivated; + } + + @Override + public void onClick(View v) { + if (mCallback != null) { + int index = (Integer) v.getTag(); + mCallback.onCrumbSelection(mCrumbs.get(index), index); + } + } + + public static class SavedStateWrapper implements Parcelable { + + public final int mActive; + public final List mCrumbs; + public final int mVisibility; + + public SavedStateWrapper(BreadCrumbLayout view) { + mActive = view.mActive; + mCrumbs = view.mCrumbs; + mVisibility = view.getVisibility(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.mActive); + dest.writeTypedList(mCrumbs); + dest.writeInt(this.mVisibility); + } + + protected SavedStateWrapper(Parcel in) { + this.mActive = in.readInt(); + this.mCrumbs = in.createTypedArrayList(Crumb.CREATOR); + this.mVisibility = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + public SavedStateWrapper createFromParcel(Parcel source) { + return new SavedStateWrapper(source); + } + + public SavedStateWrapper[] newArray(int size) { + return new SavedStateWrapper[size]; + } + }; + } + + public SavedStateWrapper getStateWrapper() { + return new SavedStateWrapper(this); + } + + public void restoreFromStateWrapper(SavedStateWrapper mSavedState) { + if (mSavedState != null) { + mActive = mSavedState.mActive; + for (Crumb c : mSavedState.mCrumbs) { + addCrumb(c, false); + } + requestLayout(); + setVisibility(mSavedState.mVisibility); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java b/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java new file mode 100644 index 00000000..bfbef58b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/CircularImageView.java @@ -0,0 +1,310 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.util.Log; + +import code.name.monkey.retromusic.R; + +public class CircularImageView extends AppCompatImageView { + + private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; + + // Default Values + private static final float DEFAULT_BORDER_WIDTH = 4; + private static final float DEFAULT_SHADOW_RADIUS = 8.0f; + + // Properties + private float borderWidth; + private int canvasSize; + private float shadowRadius; + private int shadowColor = Color.BLACK; + + // Object used to draw + private Bitmap image; + private Drawable drawable; + private Paint paint; + private Paint paintBorder; + + //region Constructor & Init Method + public CircularImageView(final Context context) { + this(context, null); + } + + public CircularImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CircularImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + // Init paint + paint = new Paint(); + paint.setAntiAlias(true); + + paintBorder = new Paint(); + paintBorder.setAntiAlias(true); + + // Load the styled attributes and set their properties + TypedArray attributes = context + .obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyleAttr, 0); + + // Init Border + if (attributes.getBoolean(R.styleable.CircularImageView_civ_border, true)) { + float defaultBorderSize = + DEFAULT_BORDER_WIDTH * getContext().getResources().getDisplayMetrics().density; + setBorderWidth(attributes + .getDimension(R.styleable.CircularImageView_civ_border_width, defaultBorderSize)); + setBorderColor( + attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE)); + } + + // Init Shadow + if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) { + shadowRadius = DEFAULT_SHADOW_RADIUS; + drawShadow(attributes.getFloat(R.styleable.CircularImageView_civ_shadow_radius, shadowRadius), + attributes.getColor(R.styleable.CircularImageView_civ_shadow_color, shadowColor)); + } + attributes.recycle(); + } + //endregion + + //region Set Attr Method + public void setBorderWidth(float borderWidth) { + this.borderWidth = borderWidth; + requestLayout(); + invalidate(); + } + + public void setBorderColor(int borderColor) { + if (paintBorder != null) { + paintBorder.setColor(borderColor); + } + invalidate(); + } + + public void addShadow() { + if (shadowRadius == 0) { + shadowRadius = DEFAULT_SHADOW_RADIUS; + } + drawShadow(shadowRadius, shadowColor); + invalidate(); + } + + public void setShadowRadius(float shadowRadius) { + drawShadow(shadowRadius, shadowColor); + invalidate(); + } + + public void setShadowColor(int shadowColor) { + drawShadow(shadowRadius, shadowColor); + invalidate(); + } + + @Override + public ScaleType getScaleType() { + return SCALE_TYPE; + } + + @Override + public void setScaleType(ScaleType scaleType) { + if (scaleType != SCALE_TYPE) { + throw new IllegalArgumentException(String.format( + "ScaleType %s not supported. ScaleType.CENTER_CROP is used by default. So you don't need to use ScaleType.", + scaleType)); + } + } + //endregion + + //region Draw Method + @Override + public void onDraw(Canvas canvas) { + // Load the bitmap + loadBitmap(); + + // Check if image isn't null + if (image == null) { + return; + } + + if (!isInEditMode()) { + canvasSize = canvas.getWidth(); + if (canvas.getHeight() < canvasSize) { + canvasSize = canvas.getHeight(); + } + } + + // circleCenter is the x or y of the view's center + // radius is the radius in pixels of the cirle to be drawn + // paint contains the shader that will texture the shape + int circleCenter = (int) (canvasSize - (borderWidth * 2)) / 2; + // Draw Border + canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, + circleCenter + borderWidth - (shadowRadius + shadowRadius / 2), paintBorder); + // Draw CircularImageView + canvas.drawCircle(circleCenter + borderWidth, circleCenter + borderWidth, + circleCenter - (shadowRadius + shadowRadius / 2), paint); + } + + private void loadBitmap() { + if (this.drawable == getDrawable()) { + return; + } + + this.drawable = getDrawable(); + this.image = drawableToBitmap(this.drawable); + updateShader(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + canvasSize = w; + if (h < canvasSize) { + canvasSize = h; + } + if (image != null) { + updateShader(); + } + } + + private void drawShadow(float shadowRadius, int shadowColor) { + this.shadowRadius = shadowRadius; + this.shadowColor = shadowColor; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + setLayerType(LAYER_TYPE_SOFTWARE, paintBorder); + } + paintBorder.setShadowLayer(shadowRadius, 0.0f, shadowRadius / 2, shadowColor); + } + + private void updateShader() { + if (image == null) { + return; + } + + // Crop Center Image + image = cropBitmap(image); + + // Create Shader + BitmapShader shader = new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + + // Center Image in Shader + Matrix matrix = new Matrix(); + matrix.setScale((float) canvasSize / (float) image.getWidth(), + (float) canvasSize / (float) image.getHeight()); + shader.setLocalMatrix(matrix); + + // Set Shader in Paint + paint.setShader(shader); + } + + private Bitmap cropBitmap(Bitmap bitmap) { + Bitmap bmp; + if (bitmap.getWidth() >= bitmap.getHeight()) { + bmp = Bitmap.createBitmap( + bitmap, + bitmap.getWidth() / 2 - bitmap.getHeight() / 2, + 0, + bitmap.getHeight(), bitmap.getHeight()); + } else { + bmp = Bitmap.createBitmap( + bitmap, + 0, + bitmap.getHeight() / 2 - bitmap.getWidth() / 2, + bitmap.getWidth(), bitmap.getWidth()); + } + return bmp; + } + + private Bitmap drawableToBitmap(Drawable drawable) { + if (drawable == null) { + return null; + } else if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + int intrinsicWidth = drawable.getIntrinsicWidth(); + int intrinsicHeight = drawable.getIntrinsicHeight(); + + if (!(intrinsicWidth > 0 && intrinsicHeight > 0)) { + return null; + } + + try { + // Create Bitmap object out of the drawable + Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } catch (OutOfMemoryError e) { + // Simply return null of failed bitmap creations + Log.e(getClass().toString(), "Encountered OutOfMemoryError while generating bitmap!"); + return null; + } + } + //endregion + + //region Mesure Method + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = measureWidth(widthMeasureSpec); + int height = measureHeight(heightMeasureSpec); + /*int imageSize = (width < height) ? width : height; + setMeasuredDimension(imageSize, imageSize);*/ + setMeasuredDimension(width, height); + } + + private int measureWidth(int measureSpec) { + int result; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // The parent has determined an exact size for the child. + result = specSize; + } else if (specMode == MeasureSpec.AT_MOST) { + // The child can be as large as it wants up to the specified size. + result = specSize; + } else { + // The parent has not imposed any constraint on the child. + result = canvasSize; + } + + return result; + } + + private int measureHeight(int measureSpecHeight) { + int result; + int specMode = MeasureSpec.getMode(measureSpecHeight); + int specSize = MeasureSpec.getSize(measureSpecHeight); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else if (specMode == MeasureSpec.AT_MOST) { + // The child can be as large as it wants up to the specified size. + result = specSize; + } else { + // Measure the text (beware: ascent is a negative number) + result = canvasSize; + } + + return (result + 2); + } + //endregion +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/DrawableGradient.java b/app/src/main/java/code/name/monkey/retromusic/views/DrawableGradient.java new file mode 100644 index 00000000..257584dc --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/DrawableGradient.java @@ -0,0 +1,22 @@ +package code.name.monkey.retromusic.views; + +import android.graphics.drawable.GradientDrawable; + +public class DrawableGradient extends GradientDrawable { + public DrawableGradient(Orientation orientations, int[] colors, int shape) { + super(orientations, colors); + try { + setShape(shape); + setGradientType(GradientDrawable.LINEAR_GRADIENT); + setCornerRadius(0); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public DrawableGradient SetTransparency(int transparencyPercent) { + this.setAlpha(255 - ((255 * transparencyPercent) / 100)); + return this; + } + +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/HeightFitSquareLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/HeightFitSquareLayout.java new file mode 100755 index 00000000..788a0374 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/HeightFitSquareLayout.java @@ -0,0 +1,39 @@ +package code.name.monkey.retromusic.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class HeightFitSquareLayout extends FrameLayout { + private boolean forceSquare = true; + + public HeightFitSquareLayout(Context context) { + super(context); + } + + public HeightFitSquareLayout(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i) { + super(context, attributeSet, i); + } + + @TargetApi(21) + public HeightFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) { + super(context, attributeSet, i, i2); + } + + public void forceSquare(boolean z) { + this.forceSquare = z; + requestLayout(); + } + + protected void onMeasure(int i, int i2) { + if (this.forceSquare) { + i = i2; + } + super.onMeasure(i, i2); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/IconImageView.java b/app/src/main/java/code/name/monkey/retromusic/views/IconImageView.java new file mode 100644 index 00000000..42e3bbd2 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/IconImageView.java @@ -0,0 +1,33 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.util.AttributeSet; +import android.widget.ImageView; + +import code.name.monkey.appthemehelper.util.ATHUtil; + +import code.name.monkey.retromusic.R; + + +public class IconImageView extends android.support.v7.widget.AppCompatImageView { + public IconImageView(Context context) { + super(context); + init(context); + } + + public IconImageView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public IconImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + if (context == null) return; + setColorFilter(ATHUtil.resolveColor(context, R.attr.iconColor), PorterDuff.Mode.SRC_IN); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/LyricView.java b/app/src/main/java/code/name/monkey/retromusic/views/LyricView.java new file mode 100644 index 00000000..0f9660d3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/LyricView.java @@ -0,0 +1,876 @@ +package code.name.monkey.retromusic.views; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Looper; +import android.support.annotation.IntDef; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; + +import org.mozilla.universalchardet.UniversalDetector; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import code.name.monkey.retromusic.R; + +/** + * Created by zhengken.me on 2016/11/27. + * ClassName : LyricView + * Description : + */ +public class LyricView extends View { + + public static final int LEFT = 0; + public static final int CENTER = 1; + public static final int RIGHT = 2; + private static final String TAG = "LyricView"; + private static final float SLIDE_COEFFICIENT = 0.2f; + + private static final int UNITS_SECOND = 1000; + private static final int UNITS_MILLISECOND = 1; + + private static final int FLING_ANIMATOR_DURATION = 500 * UNITS_MILLISECOND; + + private static final int THRESHOLD_Y_VELOCITY = 1600; + + private static final int INDICATOR_ICON_PLAY_MARGIN_LEFT = 7;//dp + private static final int INDICATOR_ICON_PLAY_WIDTH = 15;//sp + private static final int INDICATOR_LINE_MARGIN = 10;//dp + private static final int INDICATOR_TIME_TEXT_SIZE = 10;//sp + private static final int INDICATOR_TIME_MARGIN_RIGHT = 7;//dp + + private static final int DEFAULT_TEXT_SIZE = 16;//sp + private static final int DEFAULT_MAX_LENGTH = 300;//dp + private static final int DEFAULT_LINE_SPACE = 25;//dp + + private int mHintColor; + private int mDefaultColor; + private int mHighLightColor; + private int mTextAlign; + + + private int mLineCount; + private int mTextSize; + private float mLineHeight; + private LyricInfo mLyricInfo; + private String mDefaultHint; + private int mMaxLength; + + private TextPaint mTextPaint; + private Paint mBtnPlayPaint; + private Paint mLinePaint; + private Paint mTimerPaint; + + private boolean mFling = false; + private ValueAnimator mFlingAnimator; + private float mScrollY = 0; + private float mLineSpace = 0; + private boolean mIsShade; + private float mShaderWidth = 0; + private int mCurrentPlayLine = 0; + private boolean mShowIndicator; + + private VelocityTracker mVelocityTracker; + private float mVelocity = 0; + private float mDownX; + private float mDownY; + private float mLastScrollY; + private boolean mUserTouch = false; + Runnable hideIndicator = () -> { + setUserTouch(false); + invalidateView(); + }; + private int maxVelocity; + private int mLineNumberUnderIndicator = 0; + private Rect mBtnPlayRect = new Rect(); + private Rect mTimerRect; + private String mDefaultTime = "00:00"; + private int mLineColor = Color.parseColor("#EFEFEF"); + private int mBtnColor = Color.parseColor("#EFEFEF"); + private List mLineFeedRecord = new ArrayList<>(); + private boolean mEnableLineFeed = false; + private int mExtraHeight = 0; + private int mTextHeight; + private String mCurrentLyricFilePath = null; + private OnPlayerClickListener mClickListener; + + public LyricView(Context context) { + super(context); + initMyView(context); + } + + public LyricView(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + getAttrs(context, attributeSet); + initMyView(context); + + } + + public LyricView(Context context, AttributeSet attributeSet, int i) { + super(context, attributeSet, i); + getAttrs(context, attributeSet); + initMyView(context); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return super.dispatchTouchEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(event); + switch (event.getAction()) { + case MotionEvent.ACTION_CANCEL: + actionCancel(event); + break; + case MotionEvent.ACTION_DOWN: + actionDown(event); + break; + case MotionEvent.ACTION_MOVE: + actionMove(event); + break; + case MotionEvent.ACTION_UP: + actionUp(event); + break; + default: + break; + } + invalidateView(); + return true; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mBtnPlayRect.set((int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT), + (int) (getHeight() * 0.5f - getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f), + (int) (getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT)), + (int) (getHeight() * 0.5f + getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f)); + mShaderWidth = getWidth() * 0.4f; + } + + @Override + protected void onDraw(Canvas canvas) { + if (scrollable()) { + if (mShowIndicator) { + drawIndicator(canvas); + } + + for (int i = 0; i < mLineCount; i++) { + float x = 0; + switch (mTextAlign) { + case LEFT: + x = INDICATOR_ICON_PLAY_MARGIN_LEFT + INDICATOR_LINE_MARGIN + mBtnPlayRect.width(); + break; + case CENTER: + x = getWidth() * 0.5f; + break; + case RIGHT: + x = getWidth() - INDICATOR_LINE_MARGIN * 2 - mTimerRect.width() - INDICATOR_ICON_PLAY_MARGIN_LEFT; + break; + } + + float y; + if (mEnableLineFeed && i > 0) { + y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY + mLineFeedRecord.get(i - 1); + } else { + y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY; + } + +// float y = getHeight() * 0.5f + i * mLineHeight - mScrollY; + if (y < 0) { + continue; + } + if (y > getHeight()) { + break; + } + if (i == mCurrentPlayLine - 1) { + mTextPaint.setColor(mHighLightColor); + } else if (i == mLineNumberUnderIndicator && mShowIndicator) { + mTextPaint.setColor(Color.LTGRAY); + } else { + mTextPaint.setColor(mDefaultColor); + } + if (mIsShade && (y > getHeight() - mShaderWidth || y < mShaderWidth)) { + if (y < mShaderWidth) { + mTextPaint.setAlpha(26 + (int) (23000.0f * y / mShaderWidth * 0.01f)); + } else { + mTextPaint.setAlpha(26 + (int) (23000.0f * (getHeight() - y) / mShaderWidth * 0.01f)); + } + } else { + mTextPaint.setAlpha(255); + } + + if (mEnableLineFeed) { + StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint, + mMaxLength, + Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + canvas.save(); + canvas.translate(x, y); + staticLayout.draw(canvas); + canvas.restore(); + } else { + canvas.drawText(mLyricInfo.songLines.get(i).content, x, y, mTextPaint); + } + } + } else { + mTextPaint.setColor(mHintColor); + canvas.drawText(mDefaultHint, getMeasuredWidth() / 2, getMeasuredHeight() / 2, mTextPaint); + } + } + + private void getAttrs(Context context, AttributeSet attrs) { + TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LyricView); + mIsShade = ta.getBoolean(R.styleable.LyricView_fadeInFadeOut, false); + mDefaultHint = ta.getString(R.styleable.LyricView_hint) != null + ? ta.getString(R.styleable.LyricView_hint) + : getResources().getString(R.string.default_hint); + mHintColor = ta.getColor(R.styleable.LyricView_hintColor, Color.parseColor("#FFFFFF")); + mDefaultColor = ta.getColor(R.styleable.LyricView_textColor, Color.parseColor("#8D8D8D")); + mHighLightColor = ta.getColor(R.styleable.LyricView_highlightColor, Color.parseColor("#FFFFFF")); + mTextSize = ta.getDimensionPixelSize(R.styleable.LyricView_textSize, (int) getRawSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE)); + mTextAlign = ta.getInt(R.styleable.LyricView_textAlign, CENTER); + mMaxLength = ta.getDimensionPixelSize(R.styleable.LyricView_maxLength, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH)); + mLineSpace = ta.getDimensionPixelSize(R.styleable.LyricView_lineSpace, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_LINE_SPACE)); + ta.recycle(); + } + + public void setShade(boolean shade) { + mIsShade = shade; + invalidateView(); + } + + public void setHintColor(int hintColor) { + mHintColor = hintColor; + invalidateView(); + } + + public void setDefaultColor(int defaultColor) { + mDefaultColor = defaultColor; + invalidateView(); + } + + public void setHighLightColor(int highLightColor) { + mHighLightColor = highLightColor; + invalidateView(); + } + + public void setTextAlign(int textAlign) { + mTextAlign = textAlign; + invalidateView(); + } + + public void setLineCount(int lineCount) { + mLineCount = lineCount; + invalidateView(); + } + + public void setTextSize(int textSize) { + mTextSize = textSize; + invalidateView(); + } + + public void setDefaultHint(String defaultHint) { + mDefaultHint = defaultHint; + invalidateView(); + } + + public void setMaxLength(int maxLength) { + mMaxLength = maxLength; + invalidateView(); + } + + public void setOnPlayerClickListener(OnPlayerClickListener mClickListener) { + this.mClickListener = mClickListener; + } + + public void setAlignment(@Alignment int alignment) { + mTextAlign = alignment; + } + + public void setCurrentTimeMillis(long current) { + scrollToCurrentTimeMillis(current); + } + + public void setLyricFile(File file) { + + if (file == null || !file.exists()) { + reset(); + mCurrentLyricFilePath = ""; + return; + } else if (file.getPath().equals(mCurrentLyricFilePath)) { + return; + } else { + mCurrentLyricFilePath = file.getPath(); + reset(); + } + try { + + FileInputStream fis = new FileInputStream(file); + byte[] buf = new byte[1024]; + UniversalDetector detector = new UniversalDetector(null); + int nread; + while ((nread = fis.read(buf)) > 0 && !detector.isDone()) { + detector.handleData(buf, 0, nread); + } + + detector.dataEnd(); + String encoding = detector.getDetectedCharset(); + if (encoding != null) { + setLyricFile(file, encoding); + } else { + setLyricFile(file, "UTF-8"); + } + detector.reset(); + fis.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void setLyricFile(File file, String charsetName) { + if (file != null && file.exists()) { + try { + setupLyricResource(new FileInputStream(file), charsetName); + + for (int i = 0; i < mLyricInfo.songLines.size(); i++) { + + StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint, + (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH), + Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + + if (staticLayout.getLineCount() > 1) { + mEnableLineFeed = true; + mExtraHeight = mExtraHeight + (staticLayout.getLineCount() - 1) * mTextHeight; + } + + mLineFeedRecord.add(i, mExtraHeight); + + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } else { + invalidateView(); + } + } + + private void setLineSpace(float lineSpace) { + if (mLineSpace != lineSpace) { + mLineSpace = getRawSize(TypedValue.COMPLEX_UNIT_DIP, lineSpace); + measureLineHeight(); + mScrollY = measureCurrentScrollY(mCurrentPlayLine); + invalidateView(); + } + } + + public void reset() { + resetView(); + } + + private void actionCancel(MotionEvent event) { + releaseVelocityTracker(); + } + + private void actionDown(MotionEvent event) { + removeCallbacks(hideIndicator); + mLastScrollY = mScrollY; + mDownX = event.getX(); + mDownY = event.getY(); + if (mFlingAnimator != null) { + mFlingAnimator.cancel(); + mFlingAnimator = null; + } + setUserTouch(true); + } + + private boolean overScrolled() { + + return scrollable() && (mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0) || mScrollY < 0); + } + + private void actionMove(MotionEvent event) { + if (scrollable()) { + final VelocityTracker tracker = mVelocityTracker; + tracker.computeCurrentVelocity(UNITS_SECOND, maxVelocity); + mScrollY = mLastScrollY + mDownY - event.getY(); + mVelocity = tracker.getYVelocity(); + measureCurrentLine(); + } + } + + private void actionUp(MotionEvent event) { + + postDelayed(hideIndicator, 3 * UNITS_SECOND); + + releaseVelocityTracker(); + + if (scrollable()) { + if (overScrolled() && mScrollY < 0) { + smoothScrollTo(0); + return; + } + if (overScrolled() && mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)) { + smoothScrollTo(mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)); + return; + } + if (Math.abs(mVelocity) > THRESHOLD_Y_VELOCITY) { + doFlingAnimator(mVelocity); + return; + } + if (mShowIndicator && clickPlayer(event)) { + if (mLineNumberUnderIndicator != mCurrentPlayLine) { + mShowIndicator = false; + if (mClickListener != null) { + setUserTouch(false); + mClickListener.onPlayerClicked(mLyricInfo.songLines.get(mLineNumberUnderIndicator).start, mLyricInfo.songLines.get(mLineNumberUnderIndicator).content); + } + } + } + } + } + + private String measureCurrentTime() { + DecimalFormat format = new DecimalFormat("00"); + if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 < mLineCount && mLineNumberUnderIndicator > 0) { + return format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 % 60); + } + if (mLyricInfo != null && mLineCount > 0 && (mLineNumberUnderIndicator - 1) >= mLineCount) { + return format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 % 60); + } + if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 <= 0) { + return format.format(mLyricInfo.songLines.get(0).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(0).start / 1000 % 60); + } + return mDefaultTime; + } + + private void drawIndicator(Canvas canvas) { + + //绘制 播放 按钮 + Path pathPlay = new Path(); + float yCoordinate = mBtnPlayRect.left + (float) Math.sqrt(Math.pow(mBtnPlayRect.width(), 2) - Math.pow(mBtnPlayRect.width() * 0.5f, 2)); + float remainWidth = mBtnPlayRect.right - yCoordinate; + + pathPlay.moveTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f); + pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() + mBtnPlayRect.height() * 0.5f); + pathPlay.lineTo(yCoordinate, mBtnPlayRect.centerY()); + pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f); + + canvas.drawPath(pathPlay, mBtnPlayPaint); + + //绘制 分割线 + Path pathLine = new Path(); + pathLine.moveTo(mBtnPlayRect.right + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN) - remainWidth, getMeasuredHeight() * 0.5f); + pathLine.lineTo(getWidth() - mTimerRect.width() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT) - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN), getHeight() * 0.5f); + canvas.drawPath(pathLine, mLinePaint); + + //绘制 时间 + canvas.drawText(measureCurrentTime(), getWidth() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT), (getHeight() + mTimerRect.height()) * 0.5f, mTimerPaint); + } + + private boolean clickPlayer(MotionEvent event) { + if (mBtnPlayRect != null && mDownX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT)) { + float upX = event.getX(); + float upY = event.getY(); + return upX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT); + } + return false; + } + + private void doFlingAnimator(float velocity) { + + float distance = (velocity / Math.abs(velocity) * (Math.abs(velocity) * SLIDE_COEFFICIENT)); + float to = Math.min(Math.max(0, (mScrollY - distance)), (mLineCount - 1) * mLineHeight + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)); + + mFlingAnimator = ValueAnimator.ofFloat(mScrollY, to); + mFlingAnimator.addUpdateListener(animation -> { + mScrollY = (float) animation.getAnimatedValue(); + measureCurrentLine(); + invalidateView(); + }); + + mFlingAnimator.addListener(new AnimatorListenerAdapter() { + + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mVelocity = 0; + mFling = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mFling = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + } + }); + + mFlingAnimator.setDuration(FLING_ANIMATOR_DURATION); + mFlingAnimator.setInterpolator(new DecelerateInterpolator()); + mFlingAnimator.start(); + } + + private void setUserTouch(boolean isUserTouch) { + if (isUserTouch) { + mUserTouch = true; + mShowIndicator = true; + } else { + mUserTouch = false; + mShowIndicator = false; + } + } + + private void releaseVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + private void initMyView(Context context) { + maxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity(); + initPaint(); + initAllBounds(); + } + + private void initAllBounds() { + setRawTextSize(mTextSize); + + setLineSpace(mLineSpace); + measureLineHeight(); + + mTimerRect = new Rect(); + mTimerPaint.getTextBounds(mDefaultTime, 0, mDefaultTime.length(), mTimerRect); + + + } + + private void initPaint() { + mTextPaint = new TextPaint(); + mTextPaint.setDither(true); + mTextPaint.setAntiAlias(true); + Typeface typeface = Typeface.createFromAsset(getContext().getAssets(), "fonts/circular_std_book.otf"); + mTextPaint.setTypeface(typeface); + + switch (mTextAlign) { + case LEFT: + mTextPaint.setTextAlign(Paint.Align.LEFT); + break; + case CENTER: + mTextPaint.setTextAlign(Paint.Align.CENTER); + break; + case RIGHT: + mTextPaint.setTextAlign(Paint.Align.RIGHT); + break; + } + + mBtnPlayPaint = new Paint(); + mBtnPlayPaint.setDither(true); + mBtnPlayPaint.setAntiAlias(true); + mBtnPlayPaint.setColor(mBtnColor); + mBtnPlayPaint.setStyle(Paint.Style.FILL_AND_STROKE); + mBtnPlayPaint.setAlpha(128); + + mLinePaint = new Paint(); + mLinePaint.setDither(true); + mLinePaint.setAntiAlias(true); + mLinePaint.setColor(mLineColor); + mLinePaint.setAlpha(64); + mLinePaint.setStrokeWidth(1.0f); + mLinePaint.setStyle(Paint.Style.STROKE); + + mTimerPaint = new Paint(); + mTimerPaint.setDither(true); + mTimerPaint.setAntiAlias(true); + mTimerPaint.setColor(Color.WHITE); + mTimerPaint.setTextAlign(Paint.Align.RIGHT); + mTimerPaint.setTextSize(getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_TIME_TEXT_SIZE)); + + + } + + private float measureCurrentScrollY(int line) { + if (mEnableLineFeed && line > 1) { + return (line - 1) * mLineHeight + mLineFeedRecord.get(line - 1); + } + return (line - 1) * mLineHeight; + } + + private void invalidateView() { + if (Looper.getMainLooper() == Looper.myLooper()) { + // 当前线程是主UI线程,直接刷新。 + invalidate(); + } else { + // 当前线程是非UI线程,post刷新。 + postInvalidate(); + } + } + + private void measureLineHeight() { + Rect lineBound = new Rect(); + mTextPaint.getTextBounds(mDefaultHint, 0, mDefaultHint.length(), lineBound); + mTextHeight = lineBound.height(); + mLineHeight = mTextHeight + mLineSpace; + } + + /** + * To measure current showing line number based on the view's scroll Y + */ + private void measureCurrentLine() { + float baseScrollY = mScrollY + mLineHeight * 0.5f; + + if (mEnableLineFeed) { + for (int i = mLyricInfo.songLines.size(); i >= 0; i--) { + if (baseScrollY > measureCurrentScrollY(i) + mLineSpace * 0.2) { + mLineNumberUnderIndicator = i - 1; + break; + } + } + } else { + mLineNumberUnderIndicator = (int) (baseScrollY / mLineHeight); + } + + + } + + private void smoothScrollTo(float toY) { + final ValueAnimator animator = ValueAnimator.ofFloat(mScrollY, toY); + animator.addUpdateListener(valueAnimator -> { + mScrollY = (Float) valueAnimator.getAnimatedValue(); + invalidateView(); + }); + + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationEnd(Animator animator) { + mFling = false; + measureCurrentLine(); + invalidateView(); + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + + @Override + public void onAnimationStart(Animator animator) { + mFling = true; + } + }); + animator.setDuration(640); + animator.setInterpolator(new LinearInterpolator()); + + animator.start(); + } + + private boolean scrollable() { + return mLyricInfo != null && mLyricInfo.songLines != null && mLyricInfo.songLines.size() > 0; + } + + private void scrollToCurrentTimeMillis(long time) { + + int position = 0; + if (scrollable()) { + for (int i = 0, size = mLineCount; i < size; i++) { + LineInfo lineInfo = mLyricInfo.songLines.get(i); + if (lineInfo != null && lineInfo.start >= time) { + position = i; + break; + } + if (i == mLineCount - 1) { + position = mLineCount; + } + } + } + if (mCurrentPlayLine != position) { + mCurrentPlayLine = position; + if (!mFling && !mUserTouch) { + smoothScrollTo(measureCurrentScrollY(position)); + } + } + } + + private void setupLyricResource(InputStream inputStream, String charsetName) { + if (inputStream != null) { + try { + LyricInfo lyricInfo = new LyricInfo(); + lyricInfo.songLines = new ArrayList<>(); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName); + BufferedReader reader = new BufferedReader(inputStreamReader); + String line; + while ((line = reader.readLine()) != null) { + analyzeLyric(lyricInfo, line); + } + reader.close(); + inputStream.close(); + inputStreamReader.close(); + + mLyricInfo = lyricInfo; + mLineCount = mLyricInfo.songLines.size(); + invalidateView(); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + invalidateView(); + } + } + + /** + * 逐行解析歌词内容 + */ + private void analyzeLyric(LyricInfo lyricInfo, String line) { + int index = line.lastIndexOf("]"); + if (line.startsWith("[offset:")) { + // time offset + lyricInfo.songOffset = Long.parseLong(line.substring(8, index).trim()); + return; + } + if (line.startsWith("[ti:")) { + // title + lyricInfo.songTitle = line.substring(4, index).trim(); + return; + } + if (line.startsWith("[ar:")) { + // artist + lyricInfo.songArtist = line.substring(4, index).trim(); + return; + } + if (line.startsWith("[al:")) { + // album + lyricInfo.songAlbum = line.substring(4, index).trim(); + return; + } + if (line.startsWith("[by:")) { + return; + } + if (index >= 9 && line.trim().length() > index + 1) { + // lyrics + LineInfo lineInfo = new LineInfo(); + lineInfo.content = line.substring(10, line.length()); + lineInfo.start = measureStartTimeMillis(line.substring(0, index)); + lyricInfo.songLines.add(lineInfo); + } + } + + /** + * 从字符串中获得时间值 + */ + private long measureStartTimeMillis(String str) { + long minute = Long.parseLong(str.substring(1, 3)); + long second = Long.parseLong(str.substring(4, 6)); + long millisecond = Long.parseLong(str.substring(7, 9)); + return millisecond + second * 1000 + minute * 60 * 1000; + } + + private void resetLyricInfo() { + if (mLyricInfo != null) { + if (mLyricInfo.songLines != null) { + mLyricInfo.songLines.clear(); + mLyricInfo.songLines = null; + } + mLyricInfo = null; + } + } + + private void resetView() { + mCurrentPlayLine = 0; + resetLyricInfo(); + invalidateView(); + mLineCount = 0; + mScrollY = 0; + mEnableLineFeed = false; + mLineFeedRecord.clear(); + mExtraHeight = 0; + } + + private float getRawSize(int unit, float size) { + Context context = getContext(); + Resources resources; + if (context == null) { + resources = Resources.getSystem(); + } else { + resources = context.getResources(); + } + return TypedValue.applyDimension(unit, size, resources.getDisplayMetrics()); + } + + private void setRawTextSize(float size) { + if (size != mTextPaint.getTextSize()) { + mTextPaint.setTextSize(size); + measureLineHeight(); + mScrollY = measureCurrentScrollY(mCurrentPlayLine); + invalidateView(); + } + } + + @IntDef({LEFT, CENTER, RIGHT}) + @Retention(RetentionPolicy.SOURCE) + public @interface Alignment { + } + + public interface OnPlayerClickListener { + void onPlayerClicked(long progress, String content); + } + + private class LyricInfo { + List songLines; + + String songArtist; + String songTitle; + String songAlbum; + + long songOffset; + } + + private class LineInfo { + String content; + long start; + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/MetalRecyclerViewPager.java b/app/src/main/java/code/name/monkey/retromusic/views/MetalRecyclerViewPager.java new file mode 100644 index 00000000..c6319bc3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/MetalRecyclerViewPager.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017. Alexander Bilchuk + * + * 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 code.name.monkey.retromusic.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.PagerSnapHelper; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SnapHelper; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.ui.adapter.base.MediaEntryViewHolder; + +public class MetalRecyclerViewPager extends RecyclerView { + + private int itemMargin; + + public MetalRecyclerViewPager(Context context) { + super(context); + init(context, null); + } + + public MetalRecyclerViewPager(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public MetalRecyclerViewPager(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + private void init(Context context, @Nullable AttributeSet attrs) { + if (attrs != null) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MetalRecyclerViewPager, 0, 0); + itemMargin = (int) typedArray.getDimension(R.styleable.MetalRecyclerViewPager_itemMargin, 0f); + typedArray.recycle(); + } + + setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)); + SnapHelper snapHelper = new PagerSnapHelper(); + snapHelper.attachToRecyclerView(this); + } + + public void setAdapter(Adapter adapter) { + if (MetalAdapter.class.isInstance(adapter)) { + MetalAdapter metalAdapter = (MetalAdapter) adapter; + metalAdapter.setItemMargin(itemMargin); + metalAdapter.updateDisplayMetrics(); + } else { + throw new IllegalArgumentException("Only MetalAdapter is allowed here"); + } + super.setAdapter(adapter); + } + + public static abstract class MetalAdapter extends RecyclerView.Adapter { + + private DisplayMetrics metrics; + private int itemMargin; + private int itemWidth; + + public MetalAdapter(@NonNull DisplayMetrics metrics) { + this.metrics = metrics; + } + + void setItemMargin(int itemMargin) { + this.itemMargin = itemMargin; + } + + void updateDisplayMetrics() { + itemWidth = metrics.widthPixels - itemMargin * 2; + } + + @Override + public void onBindViewHolder(VH holder, int position) { + int currentItemWidth = itemWidth; + + if (position == 0) { + currentItemWidth += itemMargin; + holder.rootLayout.setPadding(0, 0, 0, 0); + } else if (position == getItemCount() - 1) { + currentItemWidth += itemMargin; + holder.rootLayout.setPadding(0, 0, 0, 0); + } + + int height = holder.rootLayout.getLayoutParams().height; + holder.rootLayout.setLayoutParams(new ViewGroup.LayoutParams(currentItemWidth, height)); + } + + + } + + public static abstract class MetalViewHolder extends MediaEntryViewHolder { + + ViewGroup rootLayout; + + public MetalViewHolder(View itemView) { + super(itemView); + rootLayout = (ViewGroup) itemView.findViewById(R.id.root_layout); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java b/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java new file mode 100644 index 00000000..07a369a0 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/NetworkImageView.java @@ -0,0 +1,41 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import com.bumptech.glide.Glide; + +import code.name.monkey.retromusic.R; + +/** + * @author Hemanth S (h4h13). + */ +public class NetworkImageView extends CircularImageView { + + public NetworkImageView(Context context) { + super(context); + init(context, null); + } + + public NetworkImageView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public NetworkImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attributeSet) { + TypedArray attributes = context + .obtainStyledAttributes(attributeSet, R.styleable.NetworkImageView, 0, 0); + String url = attributes.getString(R.styleable.NetworkImageView_url_link); + Glide.with(context).load(url).asBitmap() + .error(R.drawable.ic_person_flat) + .placeholder(R.drawable.ic_person_flat) + .into(this); + attributes.recycle(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/PlayPauseDrawable.java b/app/src/main/java/code/name/monkey/retromusic/views/PlayPauseDrawable.java new file mode 100644 index 00000000..6e9cb3aa --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/PlayPauseDrawable.java @@ -0,0 +1,213 @@ +package code.name.monkey.retromusic.views; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.util.Property; +import android.view.animation.DecelerateInterpolator; + +import code.name.monkey.retromusic.R; + + +public class PlayPauseDrawable extends Drawable { + private static final long PLAY_PAUSE_ANIMATION_DURATION = 250; + + private static final Property PROGRESS = + new Property(Float.class, "progress") { + @Override + public Float get(@NonNull PlayPauseDrawable d) { + return d.getProgress(); + } + + @Override + public void set(@NonNull PlayPauseDrawable d, Float value) { + d.setProgress(value); + } + }; + + private final Path leftPauseBar = new Path(); + private final Path rightPauseBar = new Path(); + private final Paint paint = new Paint(); + private final float pauseBarWidth; + private final float pauseBarHeight; + private final float pauseBarDistance; + + private float width; + private float height; + + private float progress; + private boolean isPlay; + private boolean isPlaySet; + + private Animator animator; + + public PlayPauseDrawable(@NonNull Context context) { + final Resources res = context.getResources(); + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.FILL); + paint.setColor(Color.WHITE); + + pauseBarWidth = res.getDimensionPixelSize(R.dimen.pause_bar_width); + pauseBarHeight = res.getDimensionPixelSize(R.dimen.pause_bar_height); + pauseBarDistance = res.getDimensionPixelSize(R.dimen.pause_bar_distance); + } + + /** + * Linear interpolate between a and b with parameter t. + */ + private static float lerp(float a, float b, float t) { + return a + (b - a) * t; + } + + @Override + protected void onBoundsChange(@NonNull final Rect bounds) { + super.onBoundsChange(bounds); + if (bounds.width() > 0 && bounds.height() > 0) { + width = bounds.width(); + height = bounds.height(); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + leftPauseBar.rewind(); + rightPauseBar.rewind(); + + // The current distance between the two pause bars. + final float barDist = lerp(pauseBarDistance, 0f, progress); + // The current width of each pause bar. + float rawBarWidth = lerp(pauseBarWidth, pauseBarHeight / 1.75f, progress); + // We have to round the bar width when finishing the progress to prevent the gap + // that might occur onDraw because of a pixel is lost when casting float to int instead of rounding it. + final float barWidth = progress == 1f ? Math.round(rawBarWidth) : rawBarWidth; + // The current position of the left pause bar's top left coordinate. + final float firstBarTopLeft = lerp(0f, barWidth, progress); + // The current position of the right pause bar's top right coordinate. + final float secondBarTopRight = lerp(2f * barWidth + barDist, barWidth + barDist, progress); + + // Draw the left pause bar. The left pause bar transforms into the + // top half of the play button triangle by animating the position of the + // rectangle's top left coordinate and expanding its bottom width. + leftPauseBar.moveTo(0f, 0f); + leftPauseBar.lineTo(firstBarTopLeft, -pauseBarHeight); + leftPauseBar.lineTo(barWidth, -pauseBarHeight); + leftPauseBar.lineTo(barWidth, 0f); + leftPauseBar.close(); + + // Draw the right pause bar. The right pause bar transforms into the + // bottom half of the play button triangle by animating the position of the + // rectangle's top right coordinate and expanding its bottom width. + rightPauseBar.moveTo(barWidth + barDist, 0f); + rightPauseBar.lineTo(barWidth + barDist, -pauseBarHeight); + rightPauseBar.lineTo(secondBarTopRight, -pauseBarHeight); + rightPauseBar.lineTo(2 * barWidth + barDist, 0f); + rightPauseBar.close(); + + final int saveCount = canvas.save(); + + // Translate the play button a tiny bit to the right so it looks more centered. + canvas.translate(lerp(0f, pauseBarHeight / 8f, progress), 0f); + + // (1) Pause --> Play: rotate 0 to 90 degrees clockwise. + // (2) Play --> Pause: rotate 90 to 180 degrees clockwise. + final float rotationProgress = isPlay ? 1f - progress : progress; + final float startingRotation = isPlay ? 90f : 0f; + canvas.rotate(lerp(startingRotation, startingRotation + 90f, rotationProgress), width / 2f, height / 2f); + + // Position the pause/play button in the center of the drawable's bounds. + canvas.translate(Math.round(width / 2f - ((2f * barWidth + barDist) / 2f)), Math.round(height / 2f + (pauseBarHeight / 2f))); + + // Draw the two bars that form the animated pause/play button. + canvas.drawPath(leftPauseBar, paint); + canvas.drawPath(rightPauseBar, paint); + + canvas.restoreToCount(saveCount); + } + + @NonNull + private Animator getPausePlayAnimator() { + isPlaySet = !isPlaySet; + final Animator anim = ObjectAnimator.ofFloat(this, PROGRESS, isPlay ? 1f : 0f, isPlay ? 0f : 1f); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + isPlay = !isPlay; + } + }); + return anim; + } + + private float getProgress() { + return progress; + } + + private void setProgress(float progress) { + this.progress = progress; + invalidateSelf(); + } + + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + paint.setColorFilter(cf); + invalidateSelf(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + public void setPlay(boolean animate) { + if (animate) { + if (!isPlaySet) { + togglePlayPause(); + } + } else { + isPlaySet = true; + isPlay = true; + setProgress(1f); + } + } + + public void setPause(boolean animate) { + if (animate) { + if (isPlaySet) { + togglePlayPause(); + } + } else { + isPlaySet = false; + isPlay = false; + setProgress(0f); + } + } + + public void togglePlayPause() { + if (animator != null) { + animator.cancel(); + } + + animator = getPausePlayAnimator(); + animator.setInterpolator(new DecelerateInterpolator()); + animator.setDuration(PLAY_PAUSE_ANIMATION_DURATION); + animator.start(); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/RoundCornerFrameLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/RoundCornerFrameLayout.java new file mode 100644 index 00000000..a4654663 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/RoundCornerFrameLayout.java @@ -0,0 +1,70 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.RectF; +import android.graphics.Region; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import code.name.monkey.retromusic.R; + +/** + * Frame layout that has rounded corners (it clips content too). + * + * @author Anton Chekulaev + */ +public class RoundCornerFrameLayout extends FrameLayout { + + private final Path stencilPath = new Path(); + private float cornerRadius = 0; + + public RoundCornerFrameLayout(Context context) { + this(context, null); + } + + public RoundCornerFrameLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RoundCornerFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + TypedArray attrArray = context + .obtainStyledAttributes(attrs, R.styleable.RoundCornerFrameLayout, 0, 0); + try { + cornerRadius = attrArray.getDimension(R.styleable.RoundCornerFrameLayout_corner_radius, 0f); + } finally { + attrArray.recycle(); + } + } + + /*@Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + // compute the path + stencilPath.reset(); + stencilPath.addRoundRect(0, 0, w, h, cornerRadius, cornerRadius, Path.Direction.CW); + stencilPath.close(); + + } +*/ + @Override + protected void dispatchDraw(Canvas canvas) { + final int count = canvas.save(); + final Path path = new Path(); + final RectF rect = new RectF(0, 0, canvas.getWidth(), canvas.getHeight()); + final float[] arrayRadius = {cornerRadius, cornerRadius, cornerRadius, cornerRadius, + cornerRadius, cornerRadius, cornerRadius, cornerRadius}; + + path.addRoundRect(rect, arrayRadius, Path.Direction.CW); + canvas.clipPath(path, Region.Op.REPLACE); + canvas.clipPath(path); + + super.dispatchDraw(canvas); + + canvas.restoreToCount(count); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/RoundedBottomSheetDialogFragment.java b/app/src/main/java/code/name/monkey/retromusic/views/RoundedBottomSheetDialogFragment.java new file mode 100644 index 00000000..75458e9c --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/RoundedBottomSheetDialogFragment.java @@ -0,0 +1,30 @@ +package code.name.monkey.retromusic.views; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.BottomSheetDialog; +import android.support.design.widget.BottomSheetDialogFragment; + +import code.name.monkey.retromusic.R; +import code.name.monkey.retromusic.util.PreferenceUtil; + +/** + * Created by yu on 2016/11/10. + */ +@SuppressLint("RestrictedApi") +public class RoundedBottomSheetDialogFragment extends BottomSheetDialogFragment { + @Override + public int getTheme() { + //noinspection ConstantConditions + return PreferenceUtil.getInstance(getContext()).getGeneralTheme() == R.style.Theme_RetroMusic_Light ? R.style.BottomSheetDialogTheme : R.style.BottomSheetDialogThemeDark; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + //noinspection ConstantConditions + return new BottomSheetDialog(getContext(), getTheme()); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/SansFontCollapsingToolbarLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/SansFontCollapsingToolbarLayout.java new file mode 100644 index 00000000..8c64add3 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/SansFontCollapsingToolbarLayout.java @@ -0,0 +1,41 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.graphics.Typeface; +import android.support.design.widget.CollapsingToolbarLayout; +import android.util.AttributeSet; + +import code.name.monkey.appthemehelper.util.TypefaceHelper; +import code.name.monkey.retromusic.R; + +/** + * @author Hemanth S (h4h13). + */ + +public class SansFontCollapsingToolbarLayout extends CollapsingToolbarLayout { + public SansFontCollapsingToolbarLayout(Context context) { + super(context); + init(context); + } + + public SansFontCollapsingToolbarLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public SansFontCollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + Typeface typefaceBold = TypefaceHelper.get(context, getResources().getString(R.string.sans_bold)); + setExpandedTitleTypeface(typefaceBold); + setCollapsedTitleTypeface(typefaceBold); + + } + + public void setTitle(int i) { + setTitle(getContext().getString(i)); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java b/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java new file mode 100644 index 00000000..023480b9 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/VerticalTextView.java @@ -0,0 +1,44 @@ +package code.name.monkey.retromusic.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.Gravity; + +public class VerticalTextView extends android.support.v7.widget.AppCompatTextView { + final boolean topDown; + + public VerticalTextView(Context context, AttributeSet attrs) { + super(context, attrs); + final int gravity = getGravity(); + if (Gravity.isVertical(gravity) && (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { + setGravity((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) | Gravity.TOP); + topDown = false; + } else + topDown = true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(heightMeasureSpec, widthMeasureSpec); + setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth()); + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + return super.setFrame(l, t, l + (b - t), t + (r - l)); + } + + @Override + public void draw(Canvas canvas) { + if (topDown) { + canvas.translate(getHeight(), 0); + canvas.rotate(90); + } else { + canvas.translate(0, getWidth()); + canvas.rotate(-90); + } + canvas.clipRect(0, 0, getWidth(), getHeight(), android.graphics.Region.Op.REPLACE); + super.draw(canvas); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/views/WidthFitSquareLayout.java b/app/src/main/java/code/name/monkey/retromusic/views/WidthFitSquareLayout.java new file mode 100755 index 00000000..8e0ec1c4 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/views/WidthFitSquareLayout.java @@ -0,0 +1,39 @@ +package code.name.monkey.retromusic.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class WidthFitSquareLayout extends FrameLayout { + private boolean forceSquare = true; + + public WidthFitSquareLayout(Context context) { + super(context); + } + + public WidthFitSquareLayout(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + public WidthFitSquareLayout(Context context, AttributeSet attributeSet, int i) { + super(context, attributeSet, i); + } + + @TargetApi(21) + public WidthFitSquareLayout(Context context, AttributeSet attributeSet, int i, int i2) { + super(context, attributeSet, i, i2); + } + + public void forceSquare(boolean z) { + this.forceSquare = z; + requestLayout(); + } + + protected void onMeasure(int i, int i2) { + if (this.forceSquare) { + i2 = i; + } + super.onMeasure(i, i2); + } +} diff --git a/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java new file mode 100644 index 00000000..7938da1d --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeContentObserver.java @@ -0,0 +1,46 @@ +package code.name.monkey.retromusic.volume; + +import android.database.ContentObserver; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Handler; +import android.support.annotation.NonNull; + +public class AudioVolumeContentObserver extends ContentObserver { + + private final OnAudioVolumeChangedListener mListener; + private final AudioManager mAudioManager; + private final int mAudioStreamType; + private int mLastVolume; + + AudioVolumeContentObserver(@NonNull Handler handler, @NonNull AudioManager audioManager, + int audioStreamType, + @NonNull OnAudioVolumeChangedListener listener) { + + super(handler); + mAudioManager = audioManager; + mAudioStreamType = audioStreamType; + mListener = listener; + mLastVolume = audioManager.getStreamVolume(mAudioStreamType); + } + + /** + * Depending on the handler this method may be executed on the UI thread + */ + @Override + public void onChange(boolean selfChange, Uri uri) { + if (mAudioManager != null && mListener != null) { + int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType); + int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType); + if (currentVolume != mLastVolume) { + mLastVolume = currentVolume; + mListener.onAudioVolumeChanged(currentVolume, maxVolume); + } + } + } + + @Override + public boolean deliverSelfNotifications() { + return super.deliverSelfNotifications(); + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeObserver.java b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeObserver.java new file mode 100644 index 00000000..53ff9593 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/volume/AudioVolumeObserver.java @@ -0,0 +1,45 @@ +package code.name.monkey.retromusic.volume; + +import android.content.Context; +import android.media.AudioManager; +import android.os.Handler; +import android.support.annotation.NonNull; + +public class AudioVolumeObserver { + + private final Context mContext; + private final AudioManager mAudioManager; + private AudioVolumeContentObserver mAudioVolumeContentObserver; + + public AudioVolumeObserver(@NonNull Context context) { + mContext = context; + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + } + + public void register(int audioStreamType, @NonNull OnAudioVolumeChangedListener listener) { + + Handler handler = new Handler(); + // with this handler AudioVolumeContentObserver#onChange() + // will be executed in the main thread + // To execute in another thread you can use a Looper + // +info: https://stackoverflow.com/a/35261443/904907 + + mAudioVolumeContentObserver = new AudioVolumeContentObserver( + handler, + mAudioManager, + audioStreamType, + listener); + + mContext.getContentResolver().registerContentObserver( + android.provider.Settings.System.CONTENT_URI, + true, + mAudioVolumeContentObserver); + } + + public void unregister() { + if (mAudioVolumeContentObserver != null) { + mContext.getContentResolver().unregisterContentObserver(mAudioVolumeContentObserver); + mAudioVolumeContentObserver = null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/volume/OnAudioVolumeChangedListener.java b/app/src/main/java/code/name/monkey/retromusic/volume/OnAudioVolumeChangedListener.java new file mode 100644 index 00000000..5c13f34b --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/volume/OnAudioVolumeChangedListener.java @@ -0,0 +1,6 @@ +package code.name.monkey.retromusic.volume; + +public interface OnAudioVolumeChangedListener { + + void onAudioVolumeChanged(int currentVolume, int maxVolume); +} \ No newline at end of file diff --git a/app/src/main/res/anim/bounce.xml b/app/src/main/res/anim/bounce.xml new file mode 100644 index 00000000..32cafd2f --- /dev/null +++ b/app/src/main/res/anim/bounce.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/grid_layout_animation_from_bottom.xml b/app/src/main/res/anim/grid_layout_animation_from_bottom.xml new file mode 100644 index 00000000..2ef4ca16 --- /dev/null +++ b/app/src/main/res/anim/grid_layout_animation_from_bottom.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/item_animation_fade.xml b/app/src/main/res/anim/item_animation_fade.xml new file mode 100644 index 00000000..29d4c38a --- /dev/null +++ b/app/src/main/res/anim/item_animation_fade.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/item_animation_fall_down.xml b/app/src/main/res/anim/item_animation_fall_down.xml new file mode 100644 index 00000000..a13e21e6 --- /dev/null +++ b/app/src/main/res/anim/item_animation_fall_down.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/item_animation_from_right.xml b/app/src/main/res/anim/item_animation_from_right.xml new file mode 100644 index 00000000..f59e44d0 --- /dev/null +++ b/app/src/main/res/anim/item_animation_from_right.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/item_animation_slide_from_bottom.xml b/app/src/main/res/anim/item_animation_slide_from_bottom.xml new file mode 100644 index 00000000..4fe1e244 --- /dev/null +++ b/app/src/main/res/anim/item_animation_slide_from_bottom.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/layout_animation_fade.xml b/app/src/main/res/anim/layout_animation_fade.xml new file mode 100644 index 00000000..a81df4bb --- /dev/null +++ b/app/src/main/res/anim/layout_animation_fade.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/layout_animation_fall_down.xml b/app/src/main/res/anim/layout_animation_fall_down.xml new file mode 100644 index 00000000..6736b9f7 --- /dev/null +++ b/app/src/main/res/anim/layout_animation_fall_down.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/layout_animation_slide_from_bottom.xml b/app/src/main/res/anim/layout_animation_slide_from_bottom.xml new file mode 100644 index 00000000..59f55f1d --- /dev/null +++ b/app/src/main/res/anim/layout_animation_slide_from_bottom.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/layout_animation_slide_right.xml b/app/src/main/res/anim/layout_animation_slide_right.xml new file mode 100644 index 00000000..9ea2fa97 --- /dev/null +++ b/app/src/main/res/anim/layout_animation_slide_right.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/animator/slide_down.xml b/app/src/main/res/animator/slide_down.xml new file mode 100644 index 00000000..14d15cb5 --- /dev/null +++ b/app/src/main/res/animator/slide_down.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/animator/slide_up.xml b/app/src/main/res/animator/slide_up.xml new file mode 100644 index 00000000..bc065bf2 --- /dev/null +++ b/app/src/main/res/animator/slide_up.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/default_album_art.webp b/app/src/main/res/drawable-hdpi/default_album_art.webp new file mode 100644 index 00000000..e613740a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/default_album_art.webp differ diff --git a/app/src/main/res/drawable-hdpi/default_artist_art.webp b/app/src/main/res/drawable-hdpi/default_artist_art.webp new file mode 100644 index 00000000..80da81cb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/default_artist_art.webp differ diff --git a/app/src/main/res/drawable-hdpi/ic_notification.png b/app/src/main/res/drawable-hdpi/ic_notification.png new file mode 100644 index 00000000..f151b30a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notification.png differ diff --git a/app/src/main/res/drawable-mdpi/default_album_art.webp b/app/src/main/res/drawable-mdpi/default_album_art.webp new file mode 100644 index 00000000..ce3c5b38 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/default_album_art.webp differ diff --git a/app/src/main/res/drawable-mdpi/default_artist_art.webp b/app/src/main/res/drawable-mdpi/default_artist_art.webp new file mode 100644 index 00000000..f51df8bb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/default_artist_art.webp differ diff --git a/app/src/main/res/drawable-mdpi/ic_notification.png b/app/src/main/res/drawable-mdpi/ic_notification.png new file mode 100644 index 00000000..611a6484 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notification.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..83aed83c --- /dev/null +++ b/app/src/main/res/drawable-v21/notification_selector.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/rect_selector.xml b/app/src/main/res/drawable-v21/rect_selector.xml new file mode 100644 index 00000000..08954384 --- /dev/null +++ b/app/src/main/res/drawable-v21/rect_selector.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/rect_selector_dark.xml b/app/src/main/res/drawable-v21/rect_selector_dark.xml new file mode 100644 index 00000000..f76d5d15 --- /dev/null +++ b/app/src/main/res/drawable-v21/rect_selector_dark.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/rect_selector_strong.xml b/app/src/main/res/drawable-v21/rect_selector_strong.xml new file mode 100644 index 00000000..cd085cf4 --- /dev/null +++ b/app/src/main/res/drawable-v21/rect_selector_strong.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/rect_selector_strong_dark.xml b/app/src/main/res/drawable-v21/rect_selector_strong_dark.xml new file mode 100644 index 00000000..fdeae459 --- /dev/null +++ b/app/src/main/res/drawable-v21/rect_selector_strong_dark.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/round_selector.xml b/app/src/main/res/drawable-v21/round_selector.xml new file mode 100644 index 00000000..09c5b530 --- /dev/null +++ b/app/src/main/res/drawable-v21/round_selector.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/round_selector_dark.xml b/app/src/main/res/drawable-v21/round_selector_dark.xml new file mode 100644 index 00000000..0619a3df --- /dev/null +++ b/app/src/main/res/drawable-v21/round_selector_dark.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/round_selector_mask.xml b/app/src/main/res/drawable-v21/round_selector_mask.xml new file mode 100644 index 00000000..a6f3dfaa --- /dev/null +++ b/app/src/main/res/drawable-v21/round_selector_mask.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/widget_selector.xml b/app/src/main/res/drawable-v21/widget_selector.xml new file mode 100644 index 00000000..f0360419 --- /dev/null +++ b/app/src/main/res/drawable-v21/widget_selector.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/widget_selector_light.xml b/app/src/main/res/drawable-v21/widget_selector_light.xml new file mode 100644 index 00000000..86c53d73 --- /dev/null +++ b/app/src/main/res/drawable-v21/widget_selector_light.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_background.xml b/app/src/main/res/drawable-v24/ic_launcher_background.xml new file mode 100644 index 00000000..077dc79b --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_background.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/default_album_art.webp b/app/src/main/res/drawable-xhdpi/default_album_art.webp new file mode 100644 index 00000000..d2c2aab3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/default_album_art.webp differ diff --git a/app/src/main/res/drawable-xhdpi/default_artist_art.webp b/app/src/main/res/drawable-xhdpi/default_artist_art.webp new file mode 100644 index 00000000..3d4cd642 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/default_artist_art.webp differ diff --git a/app/src/main/res/drawable-xhdpi/ic_notification.png b/app/src/main/res/drawable-xhdpi/ic_notification.png new file mode 100644 index 00000000..a02b4550 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notification.png differ diff --git a/app/src/main/res/drawable-xxhdpi/default_album_art.webp b/app/src/main/res/drawable-xxhdpi/default_album_art.webp new file mode 100644 index 00000000..059faa03 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/default_album_art.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/default_artist_art.webp b/app/src/main/res/drawable-xxhdpi/default_artist_art.webp new file mode 100644 index 00000000..cbdfae6d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/default_artist_art.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification.png b/app/src/main/res/drawable-xxhdpi/ic_notification.png new file mode 100644 index 00000000..28f4890c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/default_album_art.webp b/app/src/main/res/drawable-xxxhdpi/default_album_art.webp new file mode 100644 index 00000000..df892f92 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/default_album_art.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/default_artist_art.webp b/app/src/main/res/drawable-xxxhdpi/default_artist_art.webp new file mode 100644 index 00000000..2eab1f63 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/default_artist_art.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_adaptive.webp b/app/src/main/res/drawable-xxxhdpi/np_adaptive.webp new file mode 100644 index 00000000..4cd9c432 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_adaptive.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_blur.webp b/app/src/main/res/drawable-xxxhdpi/np_blur.webp new file mode 100644 index 00000000..5baaa5e4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_blur.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_blur_card.webp b/app/src/main/res/drawable-xxxhdpi/np_blur_card.webp new file mode 100644 index 00000000..876064df Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_blur_card.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_card.webp b/app/src/main/res/drawable-xxxhdpi/np_card.webp new file mode 100644 index 00000000..a55044c5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_card.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_card_blur.webp b/app/src/main/res/drawable-xxxhdpi/np_card_blur.webp new file mode 100644 index 00000000..d40d2a42 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_card_blur.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_color.webp b/app/src/main/res/drawable-xxxhdpi/np_color.webp new file mode 100644 index 00000000..c662015c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_color.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_flat.webp b/app/src/main/res/drawable-xxxhdpi/np_flat.webp new file mode 100644 index 00000000..f479144b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_flat.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_full.webp b/app/src/main/res/drawable-xxxhdpi/np_full.webp new file mode 100644 index 00000000..96f66d30 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_full.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_holiday.webp b/app/src/main/res/drawable-xxxhdpi/np_holiday.webp new file mode 100644 index 00000000..697a9cb8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_holiday.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_normal.webp b/app/src/main/res/drawable-xxxhdpi/np_normal.webp new file mode 100644 index 00000000..e943b35c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_normal.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_plain.webp b/app/src/main/res/drawable-xxxhdpi/np_plain.webp new file mode 100644 index 00000000..711c56b4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_plain.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_simple.webp b/app/src/main/res/drawable-xxxhdpi/np_simple.webp new file mode 100644 index 00000000..3a53aa96 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_simple.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/np_tiny.webp b/app/src/main/res/drawable-xxxhdpi/np_tiny.webp new file mode 100644 index 00000000..0e00061c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/np_tiny.webp differ diff --git a/app/src/main/res/drawable/abs_history_playlist.xml b/app/src/main/res/drawable/abs_history_playlist.xml new file mode 100644 index 00000000..59cbe320 --- /dev/null +++ b/app/src/main/res/drawable/abs_history_playlist.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/abs_last_added_playlist.xml b/app/src/main/res/drawable/abs_last_added_playlist.xml new file mode 100644 index 00000000..00f45bc1 --- /dev/null +++ b/app/src/main/res/drawable/abs_last_added_playlist.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/abs_shuffle.xml b/app/src/main/res/drawable/abs_shuffle.xml new file mode 100644 index 00000000..ddd4e70a --- /dev/null +++ b/app/src/main/res/drawable/abs_shuffle.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/abs_timer.xml b/app/src/main/res/drawable/abs_timer.xml new file mode 100644 index 00000000..9bd6f56d --- /dev/null +++ b/app/src/main/res/drawable/abs_timer.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/abs_top_tracks_playlist.xml b/app/src/main/res/drawable/abs_top_tracks_playlist.xml new file mode 100644 index 00000000..045a506d --- /dev/null +++ b/app/src/main/res/drawable/abs_top_tracks_playlist.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_image.xml b/app/src/main/res/drawable/background_image.xml new file mode 100644 index 00000000..34810f56 --- /dev/null +++ b/app/src/main/res/drawable/background_image.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_bottom_sheet_dialog_fragment.xml b/app/src/main/res/drawable/bg_bottom_sheet_dialog_fragment.xml new file mode 100644 index 00000000..fbe53f28 --- /dev/null +++ b/app/src/main/res/drawable/bg_bottom_sheet_dialog_fragment.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/black_overlay.xml b/app/src/main/res/drawable/black_overlay.xml new file mode 100644 index 00000000..46e84cfe --- /dev/null +++ b/app/src/main/res/drawable/black_overlay.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card.xml b/app/src/main/res/drawable/card.xml new file mode 100644 index 00000000..ace882f3 --- /dev/null +++ b/app/src/main/res/drawable/card.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circler_corners.xml b/app/src/main/res/drawable/circler_corners.xml new file mode 100644 index 00000000..74954d1a --- /dev/null +++ b/app/src/main/res/drawable/circler_corners.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/color_circle_gradient.xml b/app/src/main/res/drawable/color_circle_gradient.xml new file mode 100644 index 00000000..119aa97f --- /dev/null +++ b/app/src/main/res/drawable/color_circle_gradient.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/color_gradient.xml b/app/src/main/res/drawable/color_gradient.xml new file mode 100755 index 00000000..fe3a193c --- /dev/null +++ b/app/src/main/res/drawable/color_gradient.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/color_progress_seek.xml b/app/src/main/res/drawable/color_progress_seek.xml new file mode 100755 index 00000000..8091c388 --- /dev/null +++ b/app/src/main/res/drawable/color_progress_seek.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient.xml b/app/src/main/res/drawable/gradient.xml new file mode 100644 index 00000000..dd54e4c6 --- /dev/null +++ b/app/src/main/res/drawable/gradient.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_1.xml b/app/src/main/res/drawable/gradient_1.xml new file mode 100644 index 00000000..056d7bfd --- /dev/null +++ b/app/src/main/res/drawable/gradient_1.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_2.xml b/app/src/main/res/drawable/gradient_2.xml new file mode 100644 index 00000000..d19895a2 --- /dev/null +++ b/app/src/main/res/drawable/gradient_2.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_3.xml b/app/src/main/res/drawable/gradient_3.xml new file mode 100644 index 00000000..25b845f8 --- /dev/null +++ b/app/src/main/res/drawable/gradient_3.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_4.xml b/app/src/main/res/drawable/gradient_4.xml new file mode 100644 index 00000000..beebb803 --- /dev/null +++ b/app/src/main/res/drawable/gradient_4.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_5.xml b/app/src/main/res/drawable/gradient_5.xml new file mode 100644 index 00000000..cf540a29 --- /dev/null +++ b/app/src/main/res/drawable/gradient_5.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_6.xml b/app/src/main/res/drawable/gradient_6.xml new file mode 100644 index 00000000..a48fbb39 --- /dev/null +++ b/app/src/main/res/drawable/gradient_6.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_7.xml b/app/src/main/res/drawable/gradient_7.xml new file mode 100644 index 00000000..975e235c --- /dev/null +++ b/app/src/main/res/drawable/gradient_7.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gradient_8.xml b/app/src/main/res/drawable/gradient_8.xml new file mode 100644 index 00000000..bb56a690 --- /dev/null +++ b/app/src/main/res/drawable/gradient_8.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/hemanth_s.webp b/app/src/main/res/drawable/hemanth_s.webp new file mode 100644 index 00000000..7348b33f Binary files /dev/null and b/app/src/main/res/drawable/hemanth_s.webp differ diff --git a/app/src/main/res/drawable/holiday_background.xml b/app/src/main/res/drawable/holiday_background.xml new file mode 100644 index 00000000..55adb780 --- /dev/null +++ b/app/src/main/res/drawable/holiday_background.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_access_time_white_24dp.xml b/app/src/main/res/drawable/ic_access_time_white_24dp.xml new file mode 100644 index 00000000..5562133e --- /dev/null +++ b/app/src/main/res/drawable/ic_access_time_white_24dp.xml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_album_white_24dp.xml b/app/src/main/res/drawable/ic_album_white_24dp.xml new file mode 100644 index 00000000..6ae8cd83 --- /dev/null +++ b/app/src/main/res/drawable/ic_album_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_app_icon_with_shadow.xml b/app/src/main/res/drawable/ic_app_icon_with_shadow.xml new file mode 100644 index 00000000..9eaf3242 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_icon_with_shadow.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_app_shortcut_background.xml b/app/src/main/res/drawable/ic_app_shortcut_background.xml new file mode 100644 index 00000000..914648b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_background.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_app_shortcut_last_added.xml b/app/src/main/res/drawable/ic_app_shortcut_last_added.xml new file mode 100644 index 00000000..b80363f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_last_added.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_app_shortcut_shuffle_all.xml b/app/src/main/res/drawable/ic_app_shortcut_shuffle_all.xml new file mode 100644 index 00000000..33fccfc9 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_shuffle_all.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_app_shortcut_top_tracks.xml b/app/src/main/res/drawable/ic_app_shortcut_top_tracks.xml new file mode 100644 index 00000000..02fa94e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_top_tracks.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml new file mode 100644 index 00000000..bcce82ff --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_artist_white_24dp.xml b/app/src/main/res/drawable/ic_artist_white_24dp.xml new file mode 100644 index 00000000..87433a4b --- /dev/null +++ b/app/src/main/res/drawable/ic_artist_white_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_audio_tag_square.xml b/app/src/main/res/drawable/ic_audio_tag_square.xml new file mode 100644 index 00000000..3cfbf30c --- /dev/null +++ b/app/src/main/res/drawable/ic_audio_tag_square.xml @@ -0,0 +1,16 @@ + + + diff --git a/app/src/main/res/drawable/ic_audiotrack_black_24dp.xml b/app/src/main/res/drawable/ic_audiotrack_black_24dp.xml new file mode 100644 index 00000000..54dbfa78 --- /dev/null +++ b/app/src/main/res/drawable/ic_audiotrack_black_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_beer_white_24dp.xml b/app/src/main/res/drawable/ic_beer_white_24dp.xml new file mode 100644 index 00000000..347dc731 --- /dev/null +++ b/app/src/main/res/drawable/ic_beer_white_24dp.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_bookmark_music_white_24dp.xml b/app/src/main/res/drawable/ic_bookmark_music_white_24dp.xml new file mode 100644 index 00000000..fa10a8fa --- /dev/null +++ b/app/src/main/res/drawable/ic_bookmark_music_white_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_card_giftcard_white_24dp.xml b/app/src/main/res/drawable/ic_card_giftcard_white_24dp.xml new file mode 100644 index 00000000..2d7e2a83 --- /dev/null +++ b/app/src/main/res/drawable/ic_card_giftcard_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close_white_24dp.xml b/app/src/main/res/drawable/ic_close_white_24dp.xml new file mode 100644 index 00000000..bbd0e311 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cookie_white_24dp.xml b/app/src/main/res/drawable/ic_cookie_white_24dp.xml new file mode 100644 index 00000000..7d6ea675 --- /dev/null +++ b/app/src/main/res/drawable/ic_cookie_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete_white_24dp.xml b/app/src/main/res/drawable/ic_delete_white_24dp.xml new file mode 100644 index 00000000..3a2050bf --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_discord_white_24dp.xml b/app/src/main/res/drawable/ic_discord_white_24dp.xml new file mode 100644 index 00000000..807fe077 --- /dev/null +++ b/app/src/main/res/drawable/ic_discord_white_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_drag_vertical_white_24dp.xml b/app/src/main/res/drawable/ic_drag_vertical_white_24dp.xml new file mode 100755 index 00000000..402b4b9e --- /dev/null +++ b/app/src/main/res/drawable/ic_drag_vertical_white_24dp.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_edit_white_24dp.xml b/app/src/main/res/drawable/ic_edit_white_24dp.xml new file mode 100644 index 00000000..7a19f313 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_facebook.xml b/app/src/main/res/drawable/ic_facebook.xml new file mode 100644 index 00000000..6b2fa5b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_facebook.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fast_food_meal_white_24dp.xml b/app/src/main/res/drawable/ic_fast_food_meal_white_24dp.xml new file mode 100644 index 00000000..4598c484 --- /dev/null +++ b/app/src/main/res/drawable/ic_fast_food_meal_white_24dp.xml @@ -0,0 +1,30 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml b/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml new file mode 100644 index 00000000..5e0d0bde --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_border_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_favorite_white_24dp.xml b/app/src/main/res/drawable/ic_favorite_white_24dp.xml new file mode 100644 index 00000000..66529059 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_music_white_24dp.xml b/app/src/main/res/drawable/ic_file_music_white_24dp.xml new file mode 100644 index 00000000..efb25ba9 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_music_white_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_flag_white_24dp.xml b/app/src/main/res/drawable/ic_flag_white_24dp.xml new file mode 100644 index 00000000..a5658d37 --- /dev/null +++ b/app/src/main/res/drawable/ic_flag_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_folder_white_24dp.xml b/app/src/main/res/drawable/ic_folder_white_24dp.xml new file mode 100644 index 00000000..13fb2774 --- /dev/null +++ b/app/src/main/res/drawable/ic_folder_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_format_color_fill.xml b/app/src/main/res/drawable/ic_format_color_fill.xml new file mode 100644 index 00000000..ead2a834 --- /dev/null +++ b/app/src/main/res/drawable/ic_format_color_fill.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_github_circle_white_24dp.xml b/app/src/main/res/drawable/ic_github_circle_white_24dp.xml new file mode 100644 index 00000000..b2446883 --- /dev/null +++ b/app/src/main/res/drawable/ic_github_circle_white_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_google_circles_communities_white_24dp.xml b/app/src/main/res/drawable/ic_google_circles_communities_white_24dp.xml new file mode 100644 index 00000000..58ee29f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_google_circles_communities_white_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_google_plus_white_24dp.xml b/app/src/main/res/drawable/ic_google_plus_white_24dp.xml new file mode 100644 index 00000000..6c2c66b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_google_plus_white_24dp.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_hdr_strong_white_24dp.xml b/app/src/main/res/drawable/ic_hdr_strong_white_24dp.xml new file mode 100644 index 00000000..b788254c --- /dev/null +++ b/app/src/main/res/drawable/ic_hdr_strong_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_help_white_24dp.xml b/app/src/main/res/drawable/ic_help_white_24dp.xml new file mode 100644 index 00000000..d264f1e8 --- /dev/null +++ b/app/src/main/res/drawable/ic_help_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_white_24dp.xml b/app/src/main/res/drawable/ic_home_white_24dp.xml new file mode 100644 index 00000000..1d64d019 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_image_white_24dp.xml b/app/src/main/res/drawable/ic_image_white_24dp.xml new file mode 100644 index 00000000..e7895e8b --- /dev/null +++ b/app/src/main/res/drawable/ic_image_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_instagram.xml b/app/src/main/res/drawable/ic_instagram.xml new file mode 100644 index 00000000..9256b833 --- /dev/null +++ b/app/src/main/res/drawable/ic_instagram.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml new file mode 100644 index 00000000..3727871a --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right_white_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right_white_24dp.xml new file mode 100644 index 00000000..faa8d22e --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_right_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_up_24dp.xml b/app/src/main/res/drawable/ic_keyboard_arrow_up_24dp.xml new file mode 100755 index 00000000..37ec57fd --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_up_24dp.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_keyboard_backspace_black_24dp.xml b/app/src/main/res/drawable/ic_keyboard_backspace_black_24dp.xml new file mode 100644 index 00000000..e1271464 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_backspace_black_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..3e01a542 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..7de11d5a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_library_add_white_24dp.xml b/app/src/main/res/drawable/ic_library_add_white_24dp.xml new file mode 100644 index 00000000..d1a6c780 --- /dev/null +++ b/app/src/main/res/drawable/ic_library_add_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_library_music_white_24dp.xml b/app/src/main/res/drawable/ic_library_music_white_24dp.xml new file mode 100644 index 00000000..d166ec3d --- /dev/null +++ b/app/src/main/res/drawable/ic_library_music_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_white_24dp.xml b/app/src/main/res/drawable/ic_menu_white_24dp.xml new file mode 100644 index 00000000..046b129d --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_mic_white_24dp.xml b/app/src/main/res/drawable/ic_mic_white_24dp.xml new file mode 100644 index 00000000..441e9045 --- /dev/null +++ b/app/src/main/res/drawable/ic_mic_white_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more_vert_white_24dp.xml b/app/src/main/res/drawable/ic_more_vert_white_24dp.xml new file mode 100644 index 00000000..c42c1dcd --- /dev/null +++ b/app/src/main/res/drawable/ic_more_vert_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications_active_white_24dp.xml b/app/src/main/res/drawable/ic_notifications_active_white_24dp.xml new file mode 100644 index 00000000..08874789 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_active_white_24dp.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_patreon.xml b/app/src/main/res/drawable/ic_patreon.xml new file mode 100644 index 00000000..2e11a0d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_patreon.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_pause_white_24dp.xml b/app/src/main/res/drawable/ic_pause_white_24dp.xml new file mode 100644 index 00000000..bc3d2e0c --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_pause_white_big.xml b/app/src/main/res/drawable/ic_pause_white_big.xml new file mode 100644 index 00000000..e3892586 --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_white_big.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_person_flat.xml b/app/src/main/res/drawable/ic_person_flat.xml new file mode 100644 index 00000000..c0c3cbdf --- /dev/null +++ b/app/src/main/res/drawable/ic_person_flat.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_person_white_24dp.xml b/app/src/main/res/drawable/ic_person_white_24dp.xml new file mode 100644 index 00000000..c917f381 --- /dev/null +++ b/app/src/main/res/drawable/ic_person_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_play_arrow_white_24dp.xml b/app/src/main/res/drawable/ic_play_arrow_white_24dp.xml new file mode 100644 index 00000000..3a75768e --- /dev/null +++ b/app/src/main/res/drawable/ic_play_arrow_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_play_arrow_white_big.xml b/app/src/main/res/drawable/ic_play_arrow_white_big.xml new file mode 100644 index 00000000..34f71d45 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_arrow_white_big.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_play_circle_filled_white_24dp.xml b/app/src/main/res/drawable/ic_play_circle_filled_white_24dp.xml new file mode 100644 index 00000000..4dcc3f3d --- /dev/null +++ b/app/src/main/res/drawable/ic_play_circle_filled_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml b/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml new file mode 100644 index 00000000..2c9a92a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist_add_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_playlist_play_white_24dp.xml b/app/src/main/res/drawable/ic_playlist_play_white_24dp.xml new file mode 100644 index 00000000..47a35405 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist_play_white_24dp.xml @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_popcorn_white_24dp.xml b/app/src/main/res/drawable/ic_popcorn_white_24dp.xml new file mode 100644 index 00000000..5f0f93fd --- /dev/null +++ b/app/src/main/res/drawable/ic_popcorn_white_24dp.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_queue_music_white_24dp.xml b/app/src/main/res/drawable/ic_queue_music_white_24dp.xml new file mode 100644 index 00000000..27472f3c --- /dev/null +++ b/app/src/main/res/drawable/ic_queue_music_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_recent_actors_white_24dp.xml b/app/src/main/res/drawable/ic_recent_actors_white_24dp.xml new file mode 100644 index 00000000..66d84251 --- /dev/null +++ b/app/src/main/res/drawable/ic_recent_actors_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_redo_white_24dp.xml b/app/src/main/res/drawable/ic_redo_white_24dp.xml new file mode 100644 index 00000000..07276a51 --- /dev/null +++ b/app/src/main/res/drawable/ic_redo_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_repeat_one_white_24dp.xml b/app/src/main/res/drawable/ic_repeat_one_white_24dp.xml new file mode 100644 index 00000000..782dfcb4 --- /dev/null +++ b/app/src/main/res/drawable/ic_repeat_one_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_repeat_white_24dp.xml b/app/src/main/res/drawable/ic_repeat_white_24dp.xml new file mode 100644 index 00000000..62cebf6d --- /dev/null +++ b/app/src/main/res/drawable/ic_repeat_white_24dp.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_rounded_corner.xml b/app/src/main/res/drawable/ic_rounded_corner.xml new file mode 100644 index 00000000..5c2c2437 --- /dev/null +++ b/app/src/main/res/drawable/ic_rounded_corner.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_save_white_24dp.xml b/app/src/main/res/drawable/ic_save_white_24dp.xml new file mode 100644 index 00000000..c9d3d399 --- /dev/null +++ b/app/src/main/res/drawable/ic_save_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_scanner_white_24dp.xml b/app/src/main/res/drawable/ic_scanner_white_24dp.xml new file mode 100644 index 00000000..28ccf065 --- /dev/null +++ b/app/src/main/res/drawable/ic_scanner_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_white_24dp.xml b/app/src/main/res/drawable/ic_search_white_24dp.xml new file mode 100644 index 00000000..4408efa2 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_select_all_white_24dp.xml b/app/src/main/res/drawable/ic_select_all_white_24dp.xml new file mode 100644 index 00000000..a7029a97 --- /dev/null +++ b/app/src/main/res/drawable/ic_select_all_white_24dp.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_white_24dp.xml b/app/src/main/res/drawable/ic_settings_white_24dp.xml new file mode 100644 index 00000000..03555a89 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share_white_24dp.xml b/app/src/main/res/drawable/ic_share_white_24dp.xml new file mode 100644 index 00000000..fbcafd12 --- /dev/null +++ b/app/src/main/res/drawable/ic_share_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_shuffle_white_24dp.xml b/app/src/main/res/drawable/ic_shuffle_white_24dp.xml new file mode 100644 index 00000000..c333702e --- /dev/null +++ b/app/src/main/res/drawable/ic_shuffle_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_skip_next_white_24dp.xml b/app/src/main/res/drawable/ic_skip_next_white_24dp.xml new file mode 100644 index 00000000..f81e98bb --- /dev/null +++ b/app/src/main/res/drawable/ic_skip_next_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_skip_previous_white_24dp.xml b/app/src/main/res/drawable/ic_skip_previous_white_24dp.xml new file mode 100644 index 00000000..ec3f1661 --- /dev/null +++ b/app/src/main/res/drawable/ic_skip_previous_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_sort_white_24dp.xml b/app/src/main/res/drawable/ic_sort_white_24dp.xml new file mode 100644 index 00000000..11cc5091 --- /dev/null +++ b/app/src/main/res/drawable/ic_sort_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_star_white_24dp.xml b/app/src/main/res/drawable/ic_star_white_24dp.xml new file mode 100644 index 00000000..a16151f0 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_take_away_coffe_white_24dp.xml b/app/src/main/res/drawable/ic_take_away_coffe_white_24dp.xml new file mode 100644 index 00000000..93f951c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_take_away_coffe_white_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_take_away_white_24dp.xml b/app/src/main/res/drawable/ic_take_away_white_24dp.xml new file mode 100644 index 00000000..efe7c722 --- /dev/null +++ b/app/src/main/res/drawable/ic_take_away_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_telegram_white.xml b/app/src/main/res/drawable/ic_telegram_white.xml new file mode 100644 index 00000000..c20227ea --- /dev/null +++ b/app/src/main/res/drawable/ic_telegram_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_theme_palette_white_24dp.xml b/app/src/main/res/drawable/ic_theme_palette_white_24dp.xml new file mode 100644 index 00000000..3673e4d0 --- /dev/null +++ b/app/src/main/res/drawable/ic_theme_palette_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_timer_white_24dp.xml b/app/src/main/res/drawable/ic_timer_white_24dp.xml new file mode 100644 index 00000000..e71a9b8e --- /dev/null +++ b/app/src/main/res/drawable/ic_timer_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_trending_up_white_24dp.xml b/app/src/main/res/drawable/ic_trending_up_white_24dp.xml new file mode 100644 index 00000000..573d84f3 --- /dev/null +++ b/app/src/main/res/drawable/ic_trending_up_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_twitter_white_24dp.xml b/app/src/main/res/drawable/ic_twitter_white_24dp.xml new file mode 100755 index 00000000..3ecff0ea --- /dev/null +++ b/app/src/main/res/drawable/ic_twitter_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_unfold_more_black_24dp.xml b/app/src/main/res/drawable/ic_unfold_more_black_24dp.xml new file mode 100644 index 00000000..6c9dac13 --- /dev/null +++ b/app/src/main/res/drawable/ic_unfold_more_black_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_vector_square.xml b/app/src/main/res/drawable/ic_vector_square.xml new file mode 100644 index 00000000..c58ff194 --- /dev/null +++ b/app/src/main/res/drawable/ic_vector_square.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_view_carousel_black_24dp.xml b/app/src/main/res/drawable/ic_view_carousel_black_24dp.xml new file mode 100644 index 00000000..41ae3b6c --- /dev/null +++ b/app/src/main/res/drawable/ic_view_carousel_black_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_volume_down_white_24dp.xml b/app/src/main/res/drawable/ic_volume_down_white_24dp.xml new file mode 100644 index 00000000..6961d21e --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_down_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_volume_off_white_24dp.xml b/app/src/main/res/drawable/ic_volume_off_white_24dp.xml new file mode 100644 index 00000000..66e655aa --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_off_white_24dp.xml @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_volume_up_white_24dp.xml b/app/src/main/res/drawable/ic_volume_up_white_24dp.xml new file mode 100644 index 00000000..e8a80cca --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_up_white_24dp.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_water_bottle_white_24dp.xml b/app/src/main/res/drawable/ic_water_bottle_white_24dp.xml new file mode 100644 index 00000000..cbd6c961 --- /dev/null +++ b/app/src/main/res/drawable/ic_water_bottle_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/line_button.xml b/app/src/main/res/drawable/line_button.xml new file mode 100644 index 00000000..ee063c5a --- /dev/null +++ b/app/src/main/res/drawable/line_button.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/lockscreen_gradient.xml b/app/src/main/res/drawable/lockscreen_gradient.xml new file mode 100644 index 00000000..10c5e26c --- /dev/null +++ b/app/src/main/res/drawable/lockscreen_gradient.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/luis_gmzz.webp b/app/src/main/res/drawable/luis_gmzz.webp new file mode 100644 index 00000000..f77b7039 Binary files /dev/null and b/app/src/main/res/drawable/luis_gmzz.webp differ diff --git a/app/src/main/res/drawable/material_design_default.webp b/app/src/main/res/drawable/material_design_default.webp new file mode 100644 index 00000000..59062009 Binary files /dev/null and b/app/src/main/res/drawable/material_design_default.webp differ diff --git a/app/src/main/res/drawable/material_icons.xml b/app/src/main/res/drawable/material_icons.xml new file mode 100644 index 00000000..f270a218 --- /dev/null +++ b/app/src/main/res/drawable/material_icons.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mini_player_progress_drawable.xml b/app/src/main/res/drawable/mini_player_progress_drawable.xml new file mode 100644 index 00000000..712902f0 --- /dev/null +++ b/app/src/main/res/drawable/mini_player_progress_drawable.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/notification_selector.xml b/app/src/main/res/drawable/notification_selector.xml new file mode 100644 index 00000000..d7867f86 --- /dev/null +++ b/app/src/main/res/drawable/notification_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_drawable_vertical.xml b/app/src/main/res/drawable/progress_drawable_vertical.xml new file mode 100644 index 00000000..07660805 --- /dev/null +++ b/app/src/main/res/drawable/progress_drawable_vertical.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rect_selector.xml b/app/src/main/res/drawable/rect_selector.xml new file mode 100644 index 00000000..b03bcafa --- /dev/null +++ b/app/src/main/res/drawable/rect_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rect_selector_dark.xml b/app/src/main/res/drawable/rect_selector_dark.xml new file mode 100644 index 00000000..68896bf4 --- /dev/null +++ b/app/src/main/res/drawable/rect_selector_dark.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rect_selector_strong.xml b/app/src/main/res/drawable/rect_selector_strong.xml new file mode 100644 index 00000000..74866e66 --- /dev/null +++ b/app/src/main/res/drawable/rect_selector_strong.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rect_selector_strong_dark.xml b/app/src/main/res/drawable/rect_selector_strong_dark.xml new file mode 100644 index 00000000..faf5f5c8 --- /dev/null +++ b/app/src/main/res/drawable/rect_selector_strong_dark.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_selected.xml b/app/src/main/res/drawable/round_selected.xml new file mode 100644 index 00000000..7b468848 --- /dev/null +++ b/app/src/main/res/drawable/round_selected.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_selected_dark.xml b/app/src/main/res/drawable/round_selected_dark.xml new file mode 100644 index 00000000..5f6350ed --- /dev/null +++ b/app/src/main/res/drawable/round_selected_dark.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_selector.xml b/app/src/main/res/drawable/round_selector.xml new file mode 100644 index 00000000..77f09a4c --- /dev/null +++ b/app/src/main/res/drawable/round_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_selector_dark.xml b/app/src/main/res/drawable/round_selector_dark.xml new file mode 100644 index 00000000..d1720184 --- /dev/null +++ b/app/src/main/res/drawable/round_selector_dark.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/round_window.xml b/app/src/main/res/drawable/round_window.xml new file mode 100755 index 00000000..7e4dcae2 --- /dev/null +++ b/app/src/main/res/drawable/round_window.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/search_gradient.xml b/app/src/main/res/drawable/search_gradient.xml new file mode 100644 index 00000000..856e9def --- /dev/null +++ b/app/src/main/res/drawable/search_gradient.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow.xml b/app/src/main/res/drawable/shadow.xml new file mode 100644 index 00000000..2ebc5646 --- /dev/null +++ b/app/src/main/res/drawable/shadow.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_down.xml b/app/src/main/res/drawable/shadow_down.xml new file mode 100755 index 00000000..623e2b6a --- /dev/null +++ b/app/src/main/res/drawable/shadow_down.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_down_edited.xml b/app/src/main/res/drawable/shadow_down_edited.xml new file mode 100644 index 00000000..7e7178e2 --- /dev/null +++ b/app/src/main/res/drawable/shadow_down_edited.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_down_strong.xml b/app/src/main/res/drawable/shadow_down_strong.xml new file mode 100755 index 00000000..cc2175df --- /dev/null +++ b/app/src/main/res/drawable/shadow_down_strong.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_left_to_right.xml b/app/src/main/res/drawable/shadow_left_to_right.xml new file mode 100644 index 00000000..16ba6f80 --- /dev/null +++ b/app/src/main/res/drawable/shadow_left_to_right.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_up.xml b/app/src/main/res/drawable/shadow_up.xml new file mode 100755 index 00000000..39160e50 --- /dev/null +++ b/app/src/main/res/drawable/shadow_up.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_up_details.xml b/app/src/main/res/drawable/shadow_up_details.xml new file mode 100644 index 00000000..b81cbed3 --- /dev/null +++ b/app/src/main/res/drawable/shadow_up_details.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_up_edited.xml b/app/src/main/res/drawable/shadow_up_edited.xml new file mode 100644 index 00000000..8c67be58 --- /dev/null +++ b/app/src/main/res/drawable/shadow_up_edited.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_up_full_theme.xml b/app/src/main/res/drawable/shadow_up_full_theme.xml new file mode 100644 index 00000000..34a8df78 --- /dev/null +++ b/app/src/main/res/drawable/shadow_up_full_theme.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_up_home.xml b/app/src/main/res/drawable/shadow_up_home.xml new file mode 100644 index 00000000..dab8e5e1 --- /dev/null +++ b/app/src/main/res/drawable/shadow_up_home.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow_up_strong.xml b/app/src/main/res/drawable/shadow_up_strong.xml new file mode 100755 index 00000000..e56f2bce --- /dev/null +++ b/app/src/main/res/drawable/shadow_up_strong.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_button_edit.xml b/app/src/main/res/drawable/shape_button_edit.xml new file mode 100644 index 00000000..49065e70 --- /dev/null +++ b/app/src/main/res/drawable/shape_button_edit.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rounded_edit.xml b/app/src/main/res/drawable/shape_rounded_edit.xml new file mode 100644 index 00000000..ead9e011 --- /dev/null +++ b/app/src/main/res/drawable/shape_rounded_edit.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shuffle_line_background.xml b/app/src/main/res/drawable/shuffle_line_background.xml new file mode 100644 index 00000000..a551082d --- /dev/null +++ b/app/src/main/res/drawable/shuffle_line_background.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/slider_thumb.xml b/app/src/main/res/drawable/slider_thumb.xml new file mode 100644 index 00000000..7ac0f609 --- /dev/null +++ b/app/src/main/res/drawable/slider_thumb.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/square_play_button.xml b/app/src/main/res/drawable/square_play_button.xml new file mode 100644 index 00000000..6fe74edc --- /dev/null +++ b/app/src/main/res/drawable/square_play_button.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/square_play_button_design.xml b/app/src/main/res/drawable/square_play_button_design.xml new file mode 100644 index 00000000..c76f83ae --- /dev/null +++ b/app/src/main/res/drawable/square_play_button_design.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/square_window.xml b/app/src/main/res/drawable/square_window.xml new file mode 100755 index 00000000..f18ea8dd --- /dev/null +++ b/app/src/main/res/drawable/square_window.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/switch_thumb_material.xml b/app/src/main/res/drawable/switch_thumb_material.xml new file mode 100755 index 00000000..8c359a89 --- /dev/null +++ b/app/src/main/res/drawable/switch_thumb_material.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/thumb_material.xml b/app/src/main/res/drawable/thumb_material.xml new file mode 100644 index 00000000..8bd8cf9b --- /dev/null +++ b/app/src/main/res/drawable/thumb_material.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toggle_outline_buttons.xml b/app/src/main/res/drawable/toggle_outline_buttons.xml new file mode 100644 index 00000000..d0f3f291 --- /dev/null +++ b/app/src/main/res/drawable/toggle_outline_buttons.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/top_corners.xml b/app/src/main/res/drawable/top_corners.xml new file mode 100644 index 00000000..d17bd811 --- /dev/null +++ b/app/src/main/res/drawable/top_corners.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_selector.xml b/app/src/main/res/drawable/widget_selector.xml new file mode 100755 index 00000000..68896bf4 --- /dev/null +++ b/app/src/main/res/drawable/widget_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_selector_dark.xml b/app/src/main/res/drawable/widget_selector_dark.xml new file mode 100644 index 00000000..d7867f86 --- /dev/null +++ b/app/src/main/res/drawable/widget_selector_dark.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_selector_light.xml b/app/src/main/res/drawable/widget_selector_light.xml new file mode 100644 index 00000000..b03bcafa --- /dev/null +++ b/app/src/main/res/drawable/widget_selector_light.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_album.xml b/app/src/main/res/layout-land/activity_album.xml new file mode 100644 index 00000000..034b439d --- /dev/null +++ b/app/src/main/res/layout-land/activity_album.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-land/activity_album_tag_editor.xml b/app/src/main/res/layout-land/activity_album_tag_editor.xml new file mode 100644 index 00000000..760b15be --- /dev/null +++ b/app/src/main/res/layout-land/activity_album_tag_editor.xml @@ -0,0 +1,165 @@ + + + + + + +