Initial commit retro music app
This commit is contained in:
parent
ab332473bc
commit
fe890632fd
932 changed files with 83126 additions and 0 deletions
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
64
app/src/main/java/code/name/monkey/retromusic/util/ColorUtils.java
Executable file
64
app/src/main/java/code/name/monkey/retromusic/util/ColorUtils.java
Executable file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<File> compressToFileAsFlowable(final File imageFile) {
|
||||
return compressToFileAsFlowable(imageFile, imageFile.getName());
|
||||
}
|
||||
|
||||
public Flowable<File> 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<Bitmap> compressToBitmapAsFlowable(final File imageFile) {
|
||||
return Flowable.defer(() -> {
|
||||
try {
|
||||
return Flowable.just(compressToBitmap(imageFile));
|
||||
} catch (IOException e) {
|
||||
return Flowable.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<Bitmap>() {
|
||||
@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<? super Bitmap> glideAnimation) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@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<Void, Void, Void>() {
|
||||
@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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
251
app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java
Normal file
251
app/src/main/java/code/name/monkey/retromusic/util/FileUtil.java
Normal file
|
@ -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<ArrayList<Song>> matchFilesWithMediaStore(@NonNull Context context,
|
||||
@Nullable List<File> 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<File> 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<File> 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<File> listFiles(@NonNull File directory, @Nullable FileFilter fileFilter) {
|
||||
List<File> fileList = new LinkedList<>();
|
||||
File[] found = directory.listFiles(fileFilter);
|
||||
if (found != null) {
|
||||
Collections.addAll(fileList, found);
|
||||
}
|
||||
return fileList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<File> listFilesDeep(@NonNull File directory, @Nullable FileFilter fileFilter) {
|
||||
List<File> files = new LinkedList<>();
|
||||
internalListFilesDeep(files, directory, fileFilter);
|
||||
return files;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<File> listFilesDeep(@NonNull Collection<File> files,
|
||||
@Nullable FileFilter fileFilter) {
|
||||
List<File> 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<File> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
69
app/src/main/java/code/name/monkey/retromusic/util/LastFMUtil.java
Executable file
69
app/src/main/java/code/name/monkey/retromusic/util/LastFMUtil.java
Executable file
|
@ -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<Image> 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<LastFmArtist.Artist.Image> 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<ImageSize, String> 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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<Song> 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<Song> 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<Pattern> 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<Playlist> getFavoritesPlaylist(@NonNull final Context context) {
|
||||
return PlaylistLoader.getPlaylist(context, context.getString(R.string.favorites));
|
||||
}
|
||||
|
||||
private static Observable<Playlist> 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("<unknown>");
|
||||
}
|
||||
|
||||
@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<Song> 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) : "-";
|
||||
}
|
||||
}
|
120
app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java
Executable file
120
app/src/main/java/code/name/monkey/retromusic/util/NavigationUtil.java
Executable file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<Playlist> 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<Song> helperList = new ArrayList<>();
|
||||
helperList.add(song);
|
||||
addToPlaylist(context, helperList, playlistId, showToastOnFinish);
|
||||
}
|
||||
|
||||
public static void addToPlaylist(@NonNull final Context context, @NonNull final List<Song> 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<Song> 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<PlaylistSong> 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<File> savePlaylist(Context context, Playlist playlist) {
|
||||
return M3UWriter.write(context, new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<CategoryInfo> getLibraryCategoryInfos() {
|
||||
String data = mPreferences.getString(LIBRARY_CATEGORIES, null);
|
||||
if (data != null) {
|
||||
Gson gson = new Gson();
|
||||
Type collectionType = new TypeToken<ArrayList<CategoryInfo>>() {
|
||||
}.getType();
|
||||
|
||||
try {
|
||||
return gson.fromJson(data, collectionType);
|
||||
} catch (JsonSyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return getDefaultLibraryCategoryInfos();
|
||||
}
|
||||
|
||||
public void setLibraryCategoryInfos(ArrayList<CategoryInfo> categories) {
|
||||
Gson gson = new Gson();
|
||||
Type collectionType = new TypeToken<ArrayList<CategoryInfo>>() {
|
||||
}.getType();
|
||||
|
||||
final SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putString(LIBRARY_CATEGORIES, gson.toJson(categories, collectionType));
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public ArrayList<CategoryInfo> getDefaultLibraryCategoryInfos() {
|
||||
ArrayList<CategoryInfo> 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);
|
||||
}
|
||||
}
|
|
@ -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<Palette.Swatch> 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<Palette.Swatch> swatchesTemp = Palette.from(bitmap).generate().getSwatches();
|
||||
List<Palette.Swatch> swatches = new ArrayList<Palette.Swatch>(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<Palette.Swatch> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
314
app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java
Executable file
314
app/src/main/java/code/name/monkey/retromusic/util/RetroUtil.java
Executable file
|
@ -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<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
|
||||
for (NetworkInterface intf : interfaces) {
|
||||
List<InetAddress> 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<Palette.Swatch> 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<Swatch> 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;
|
||||
}*/
|
||||
}
|
|
@ -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<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache =
|
||||
new WeakHashMap<Bitmap, Pair<Boolean, Integer>>();
|
||||
|
||||
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<Boolean, Integer> 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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue