Removed suggestions

This commit is contained in:
h4h13 2019-04-15 08:14:48 +05:30
parent 43cb27ae5f
commit b46175e20b
28 changed files with 145 additions and 440 deletions

View file

@ -153,6 +153,6 @@ class ArtistImageLoader(private val context: Context, private val lastFMClient:
companion object {
// we need these very low values to make sure our artist image loading calls doesn't block the image loading queue
private const val TIMEOUT = 500
private const val TIMEOUT = 700
}
}

View file

@ -12,33 +12,33 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util.schedulers;
import androidx.annotation.NonNull;
import io.reactivex.Scheduler;
import io.reactivex.schedulers.Schedulers;
package code.name.monkey.retromusic.lyrics;
/**
* Created by hemanths on 12/08/17.
* Desc : 歌词实体
* Author : Lauzy
* Date : 2017/10/13
* Blog : http://www.jianshu.com/u/e76853f863a9
* Email : freedompaladin@gmail.com
*/
public class Lrc {
private long time;
private String text;
public class ImmediateScheduler implements BaseSchedulerProvider {
@NonNull
@Override
public Scheduler computation() {
return Schedulers.trampoline();
public long getTime() {
return time;
}
@NonNull
@Override
public Scheduler io() {
return Schedulers.trampoline();
public void setTime(long time) {
this.time = time;
}
@NonNull
@Override
public Scheduler ui() {
return Schedulers.trampoline();
public String getText() {
return text;
}
}
public void setText(String text) {
this.text = text;
}
}

View file

@ -0,0 +1,151 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.lyrics;
import android.content.Context;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Desc : 歌词解析
* Author : Lauzy
* Date : 2017/10/13
* Blog : http://www.jianshu.com/u/e76853f863a9
* Email : freedompaladin@gmail.com
*/
public class LrcHelper {
private static final String CHARSET = "utf-8";
//[03:56.00][03:18.00][02:06.00][01:07.00]原谅我这一生不羁放纵爱自由
private static final String LINE_REGEX = "((\\[\\d{2}:\\d{2}\\.\\d{2}])+)(.*)";
private static final String TIME_REGEX = "\\[(\\d{2}):(\\d{2})\\.(\\d{2})]";
public static List<Lrc> parseLrcFromAssets(Context context, String fileName) {
try {
return parseInputStream(context.getResources().getAssets().open(fileName));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static List<Lrc> parseLrcFromFile(File file) {
try {
return parseInputStream(new FileInputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
private static List<Lrc> parseInputStream(InputStream inputStream) {
List<Lrc> lrcs = new ArrayList<>();
InputStreamReader isr = null;
BufferedReader br = null;
try {
isr = new InputStreamReader(inputStream, CHARSET);
br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
List<Lrc> lrcList = parseLrc(line);
if (lrcList != null && lrcList.size() != 0) {
lrcs.addAll(lrcList);
}
}
sortLrcs(lrcs);
return lrcs;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (isr != null) {
isr.close();
}
if (br != null) {
br.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
return lrcs;
}
private static void sortLrcs(List<Lrc> lrcs) {
Collections.sort(lrcs, new Comparator<Lrc>() {
@Override
public int compare(Lrc o1, Lrc o2) {
return (int) (o1.getTime() - o2.getTime());
}
});
}
private static List<Lrc> parseLrc(String lrcLine) {
if (lrcLine.trim().isEmpty()) {
return null;
}
List<Lrc> lrcs = new ArrayList<>();
Matcher matcher = Pattern.compile(LINE_REGEX).matcher(lrcLine);
if (!matcher.matches()) {
return null;
}
String time = matcher.group(1);
String content = matcher.group(3);
Matcher timeMatcher = Pattern.compile(TIME_REGEX).matcher(time);
while (timeMatcher.find()) {
String min = timeMatcher.group(1);
String sec = timeMatcher.group(2);
String mil = timeMatcher.group(3);
Lrc lrc = new Lrc();
if (content != null && content.length() != 0) {
lrc.setTime(Long.parseLong(min) * 60 * 1000 + Long.parseLong(sec) * 1000
+ Long.parseLong(mil) * 10);
lrc.setText(content);
lrcs.add(lrc);
}
}
return lrcs;
}
public static String formatTime(long time) {
int min = (int) (time / 60000);
int sec = (int) (time / 1000 % 60);
return adjustFormat(min) + ":" + adjustFormat(sec);
}
private static String adjustFormat(int time) {
if (time < 10) {
return "0" + time;
}
return time + "";
}
}

View file

@ -0,0 +1,635 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.lyrics
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Looper
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.util.AttributeSet
import android.util.TypedValue
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import android.widget.OverScroller
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
import code.name.monkey.retromusic.R
import java.util.*
/**
* Desc : 歌词
* Author : Lauzy
* Date : 2017/10/13
* Blog : http://www.jianshu.com/u/e76853f863a9
* Email : freedompaladin@gmail.com
*/
class LrcView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
private var mLrcData: MutableList<Lrc>? = null
private var mTextPaint: TextPaint? = null
private var mDefaultContent: String? = null
private var mCurrentLine: Int = 0
private var mOffset: Float = 0.toFloat()
private var mLastMotionX: Float = 0.toFloat()
private var mLastMotionY: Float = 0.toFloat()
private var mScaledTouchSlop: Int = 0
private var mOverScroller: OverScroller? = null
private var mVelocityTracker: VelocityTracker? = null
private var mMaximumFlingVelocity: Int = 0
private var mMinimumFlingVelocity: Int = 0
private var mLrcTextSize: Float = 0.toFloat()
private var mLrcLineSpaceHeight: Float = 0.toFloat()
private var mTouchDelay: Int = 0
private var mNormalColor: Int = 0
private var mCurrentPlayLineColor: Int = 0
private var mNoLrcTextSize: Float = 0.toFloat()
private var mNoLrcTextColor: Int = 0
//是否拖拽中否的话响应onClick事件
private var isDragging: Boolean = false
//用户开始操作
private var isUserScroll: Boolean = false
private var isAutoAdjustPosition = true
private var mPlayDrawable: Drawable? = null
private var isShowTimeIndicator: Boolean = false
private var mPlayRect: Rect? = null
private var mIndicatorPaint: Paint? = null
private var mIndicatorLineWidth: Float = 0.toFloat()
private var mIndicatorTextSize: Float = 0.toFloat()
private var mCurrentIndicateLineTextColor: Int = 0
private var mIndicatorLineColor: Int = 0
private var mIndicatorMargin: Float = 0.toFloat()
private var mIconLineGap: Float = 0.toFloat()
private var mIconWidth: Float = 0.toFloat()
private var mIconHeight: Float = 0.toFloat()
private var isEnableShowIndicator = true
private var mIndicatorTextColor: Int = 0
private var mIndicatorTouchDelay: Int = 0
private val mLrcMap = HashMap<String, StaticLayout>()
private val mHideIndicatorRunnable = Runnable {
isShowTimeIndicator = false
invalidateView()
}
private val mStaticLayoutHashMap = HashMap<String, StaticLayout>()
private val mScrollRunnable = Runnable {
isUserScroll = false
scrollToPosition(mCurrentLine)
}
private var mOnPlayIndicatorLineListener: OnPlayIndicatorLineListener? = null
private val lrcWidth: Int
get() = width - paddingLeft - paddingRight
private val lrcHeight: Int
get() = height
private val isLrcEmpty: Boolean
get() = mLrcData == null || lrcCount == 0
private val lrcCount: Int
get() = mLrcData!!.size
//itemOffset 和 mOffset 最小即当前位置
val indicatePosition: Int
get() {
var pos = 0
var min = java.lang.Float.MAX_VALUE
for (i in mLrcData!!.indices) {
val offsetY = getItemOffsetY(i)
val abs = Math.abs(offsetY - mOffset)
if (abs < min) {
min = abs
pos = i
}
}
return pos
}
var playDrawable: Drawable?
get() = mPlayDrawable
set(playDrawable) {
mPlayDrawable = playDrawable
mPlayDrawable!!.bounds = mPlayRect!!
invalidateView()
}
init {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView)
mLrcTextSize = typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15f).toFloat())
mLrcLineSpaceHeight = typedArray.getDimension(R.styleable.LrcView_lrcLineSpaceSize, dp2px(context, 20f).toFloat())
mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500)
mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500)
mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY)
mCurrentPlayLineColor = typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE)
mNoLrcTextSize = typedArray.getDimension(R.styleable.LrcView_noLrcTextSize, dp2px(context, 20f).toFloat())
mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK)
mIndicatorLineWidth = typedArray.getDimension(R.styleable.LrcView_indicatorLineHeight, dp2px(context, 0.5f).toFloat())
mIndicatorTextSize = typedArray.getDimension(R.styleable.LrcView_indicatorTextSize, sp2px(context, 13f).toFloat())
mIndicatorTextColor = typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY)
mCurrentIndicateLineTextColor = typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY)
mIndicatorLineColor = typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY)
mIndicatorMargin = typedArray.getDimension(R.styleable.LrcView_indicatorStartEndMargin, dp2px(context, 5f).toFloat())
mIconLineGap = typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3f).toFloat())
mIconWidth = typedArray.getDimension(R.styleable.LrcView_playIconWidth, dp2px(context, 20f).toFloat())
mIconHeight = typedArray.getDimension(R.styleable.LrcView_playIconHeight, dp2px(context, 20f).toFloat())
mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon)
mPlayDrawable = if (mPlayDrawable == null) ContextCompat.getDrawable(context, R.drawable.play_icon) else mPlayDrawable
typedArray.recycle()
setupConfigs(context)
}
private fun setupConfigs(context: Context) {
mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
mMaximumFlingVelocity = ViewConfiguration.get(context).scaledMaximumFlingVelocity
mMinimumFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity
mOverScroller = OverScroller(context, DecelerateInterpolator())
mOverScroller!!.setFriction(0.1f)
// ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f
mTextPaint = TextPaint()
mTextPaint!!.apply {
isAntiAlias = true
textAlign = Paint.Align.LEFT
textSize = mLrcTextSize
typeface = ResourcesCompat.getFont(context, R.font.circular)
}
mDefaultContent = DEFAULT_CONTENT
mIndicatorPaint = Paint()
mIndicatorPaint!!.isAntiAlias = true
mIndicatorPaint!!.strokeWidth = mIndicatorLineWidth
mIndicatorPaint!!.color = mIndicatorLineColor
mPlayRect = Rect()
mIndicatorPaint!!.textSize = mIndicatorTextSize
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
mPlayRect!!.left = mIndicatorMargin.toInt()
mPlayRect!!.top = (height / 2 - mIconHeight / 2).toInt()
mPlayRect!!.right = (mPlayRect!!.left + mIconWidth).toInt()
mPlayRect!!.bottom = (mPlayRect!!.top + mIconHeight).toInt()
mPlayDrawable!!.bounds = mPlayRect!!
}
}
fun setLrcData(lrcData: MutableList<Lrc>) {
resetView(DEFAULT_CONTENT)
mLrcData = lrcData
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (isLrcEmpty) {
drawEmptyText(canvas)
return
}
val indicatePosition = indicatePosition
mTextPaint!!.textSize = mLrcTextSize
mTextPaint!!.textAlign = Paint.Align.LEFT
var y = (lrcHeight / 2).toFloat()
val x = dip2px(context, 16f).toFloat()
for (i in 0 until lrcCount) {
if (i > 0) {
y += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
}
if (mCurrentLine == i) {
mTextPaint!!.color = mCurrentPlayLineColor
} else if (indicatePosition == i && isShowTimeIndicator) {
mTextPaint!!.color = mCurrentIndicateLineTextColor
} else {
mTextPaint!!.color = mNormalColor
}
drawLrc(canvas, x, y, i)
}
if (isShowTimeIndicator) {
mPlayDrawable!!.draw(canvas)
val time = mLrcData!![indicatePosition].time
val timeWidth = mIndicatorPaint!!.measureText(LrcHelper.formatTime(time))
mIndicatorPaint!!.color = mIndicatorLineColor
canvas.drawLine(mPlayRect!!.right + mIconLineGap, (height / 2).toFloat(),
width - timeWidth * 1.3f, (height / 2).toFloat(), mIndicatorPaint!!)
val baseX = (width - timeWidth * 1.1f).toInt()
val baseline = (height / 2).toFloat() - (mIndicatorPaint!!.descent() - mIndicatorPaint!!.ascent()) / 2 - mIndicatorPaint!!.ascent()
mIndicatorPaint!!.color = mIndicatorTextColor
canvas.drawText(LrcHelper.formatTime(time), baseX.toFloat(), baseline, mIndicatorPaint!!)
}
}
private fun dip2px(context: Context, dpVale: Float): Int {
val scale = context.resources.displayMetrics.density
return (dpVale * scale + 0.5f).toInt()
}
private fun drawLrc(canvas: Canvas, x: Float, y: Float, i: Int) {
val text = mLrcData!![i].text
var staticLayout: StaticLayout? = mLrcMap[text]
if (staticLayout == null) {
mTextPaint!!.textSize = mLrcTextSize
staticLayout = StaticLayout(text, mTextPaint, lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
mLrcMap[text] = staticLayout
}
canvas.save()
canvas.translate(x, y - (staticLayout.height / 2).toFloat() - mOffset)
staticLayout.draw(canvas)
canvas.restore()
}
//中间空文字
private fun drawEmptyText(canvas: Canvas) {
mTextPaint!!.textAlign = Paint.Align.LEFT
mTextPaint!!.color = mNoLrcTextColor
mTextPaint!!.textSize = mNoLrcTextSize
canvas.save()
val staticLayout = StaticLayout(mDefaultContent, mTextPaint,
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
val margin = dip2px(context, 16f).toFloat();
canvas.translate(margin, margin)
staticLayout.draw(canvas)
canvas.restore()
}
fun updateTime(time: Long) {
if (isLrcEmpty) {
return
}
val linePosition = getUpdateTimeLinePosition(time)
if (mCurrentLine != linePosition) {
mCurrentLine = linePosition
if (isUserScroll) {
invalidateView()
return
}
ViewCompat.postOnAnimation(this@LrcView, mScrollRunnable)
}
}
private fun getUpdateTimeLinePosition(time: Long): Int {
var linePos = 0
for (i in 0 until lrcCount) {
val lrc = mLrcData!![i]
if (time >= lrc.time) {
if (i == lrcCount - 1) {
linePos = lrcCount - 1
} else if (time < mLrcData!![i + 1].time) {
linePos = i
break
}
}
}
return linePos
}
private fun scrollToPosition(linePosition: Int) {
val scrollY = getItemOffsetY(linePosition)
val animator = ValueAnimator.ofFloat(mOffset, scrollY)
animator.addUpdateListener { animation ->
mOffset = animation.animatedValue as Float
invalidateView()
}
animator.duration = 300
animator.start()
}
private fun getItemOffsetY(linePosition: Int): Float {
var tempY = 0f
for (i in 1..linePosition) {
tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
}
return tempY
}
private fun getTextHeight(linePosition: Int): Float {
val text = mLrcData!![linePosition].text
var staticLayout: StaticLayout? = mStaticLayoutHashMap[text]
if (staticLayout == null) {
mTextPaint!!.textSize = mLrcTextSize
staticLayout = StaticLayout(text, mTextPaint,
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
mStaticLayoutHashMap[text] = staticLayout
}
return staticLayout.height.toFloat()
}
private fun overScrolled(): Boolean {
return mOffset > getItemOffsetY(lrcCount - 1) || mOffset < 0
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (isLrcEmpty) {
return super.onTouchEvent(event)
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain()
}
mVelocityTracker!!.addMovement(event)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
removeCallbacks(mScrollRunnable)
removeCallbacks(mHideIndicatorRunnable)
if (!mOverScroller!!.isFinished) {
mOverScroller!!.abortAnimation()
}
mLastMotionX = event.x
mLastMotionY = event.y
isUserScroll = true
isDragging = false
}
MotionEvent.ACTION_MOVE -> {
var moveY = event.y - mLastMotionY
if (Math.abs(moveY) > mScaledTouchSlop) {
isDragging = true
isShowTimeIndicator = isEnableShowIndicator
}
if (isDragging) {
// if (mOffset < 0) {
// mOffset = Math.max(mOffset, -getTextHeight(0) - mLrcLineSpaceHeight);
// }
val maxHeight = getItemOffsetY(lrcCount - 1)
// if (mOffset > maxHeight) {
// mOffset = Math.min(mOffset, maxHeight + getTextHeight(getLrcCount() - 1) + mLrcLineSpaceHeight);
// }
if (mOffset < 0 || mOffset > maxHeight) {
moveY /= 3.5f
}
mOffset -= moveY
mLastMotionY = event.y
invalidateView()
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
if (!isDragging && (!isShowTimeIndicator || !onClickPlayButton(event))) {
isShowTimeIndicator = false
invalidateView()
performClick()
}
handleActionUp(event)
}
}
// return isDragging || super.onTouchEvent(event);
return true
}
private fun handleActionUp(event: MotionEvent) {
if (isEnableShowIndicator) {
ViewCompat.postOnAnimationDelayed(this@LrcView, mHideIndicatorRunnable, mIndicatorTouchDelay.toLong())
}
if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) {
isShowTimeIndicator = false
invalidateView()
if (mOnPlayIndicatorLineListener != null) {
mOnPlayIndicatorLineListener!!.onPlay(mLrcData!![indicatePosition].time,
mLrcData!![indicatePosition].text)
}
}
if (overScrolled() && mOffset < 0) {
scrollToPosition(0)
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
}
return
}
if (overScrolled() && mOffset > getItemOffsetY(lrcCount - 1)) {
scrollToPosition(lrcCount - 1)
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
}
return
}
mVelocityTracker!!.computeCurrentVelocity(1000, mMaximumFlingVelocity.toFloat())
val YVelocity = mVelocityTracker!!.yVelocity
val absYVelocity = Math.abs(YVelocity)
if (absYVelocity > mMinimumFlingVelocity) {
mOverScroller!!.fling(0, mOffset.toInt(), 0, (-YVelocity).toInt(), 0,
0, 0, getItemOffsetY(lrcCount - 1).toInt(),
0, getTextHeight(0).toInt())
invalidateView()
}
releaseVelocityTracker()
if (isAutoAdjustPosition) {
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
}
}
private fun onClickPlayButton(event: MotionEvent): Boolean {
val left = mPlayRect!!.left.toFloat()
val right = mPlayRect!!.right.toFloat()
val top = mPlayRect!!.top.toFloat()
val bottom = mPlayRect!!.bottom.toFloat()
val x = event.x
val y = event.y
return (mLastMotionX > left && mLastMotionX < right && mLastMotionY > top
&& mLastMotionY < bottom && x > left && x < right && y > top && y < bottom)
}
override fun computeScroll() {
super.computeScroll()
if (mOverScroller!!.computeScrollOffset()) {
mOffset = mOverScroller!!.currY.toFloat()
invalidateView()
}
}
private fun releaseVelocityTracker() {
if (null != mVelocityTracker) {
mVelocityTracker!!.clear()
mVelocityTracker!!.recycle()
mVelocityTracker = null
}
}
fun resetView(defaultContent: String) {
if (mLrcData != null) {
mLrcData!!.clear()
}
mLrcMap.clear()
mStaticLayoutHashMap.clear()
mCurrentLine = 0
mOffset = 0f
isUserScroll = false
isDragging = false
mDefaultContent = defaultContent
removeCallbacks(mScrollRunnable)
invalidate()
}
override fun performClick(): Boolean {
return super.performClick()
}
fun dp2px(context: Context, dpVal: Float): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.resources.displayMetrics).toInt()
}
fun sp2px(context: Context, spVal: Float): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
spVal, context.resources.displayMetrics).toInt()
}
/**
* 暂停手动滑动歌词后不再自动回滚至当前播放位置
*/
fun pause() {
isAutoAdjustPosition = false
invalidateView()
}
/**
* 恢复继续自动回滚
*/
fun resume() {
isAutoAdjustPosition = true
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
invalidateView()
}
/*------------------Config-------------------*/
private fun invalidateView() {
if (Looper.getMainLooper().thread === Thread.currentThread()) {
invalidate()
} else {
postInvalidate()
}
}
fun setOnPlayIndicatorLineListener(onPlayIndicatorLineListener: OnPlayIndicatorLineListener) {
mOnPlayIndicatorLineListener = onPlayIndicatorLineListener
}
fun setEmptyContent(defaultContent: String) {
mDefaultContent = defaultContent
invalidateView()
}
fun setLrcTextSize(lrcTextSize: Float) {
mLrcTextSize = lrcTextSize
invalidateView()
}
fun setLrcLineSpaceHeight(lrcLineSpaceHeight: Float) {
mLrcLineSpaceHeight = lrcLineSpaceHeight
invalidateView()
}
fun setTouchDelay(touchDelay: Int) {
mTouchDelay = touchDelay
invalidateView()
}
fun setNormalColor(@ColorInt normalColor: Int) {
mNormalColor = normalColor
invalidateView()
}
fun setCurrentPlayLineColor(@ColorInt currentPlayLineColor: Int) {
mCurrentPlayLineColor = currentPlayLineColor
invalidateView()
}
fun setNoLrcTextSize(noLrcTextSize: Float) {
mNoLrcTextSize = noLrcTextSize
invalidateView()
}
fun setNoLrcTextColor(@ColorInt noLrcTextColor: Int) {
mNoLrcTextColor = noLrcTextColor
invalidateView()
}
fun setIndicatorLineWidth(indicatorLineWidth: Float) {
mIndicatorLineWidth = indicatorLineWidth
invalidateView()
}
fun setIndicatorTextSize(indicatorTextSize: Float) {
// mIndicatorTextSize = indicatorTextSize;
mIndicatorPaint!!.textSize = indicatorTextSize
invalidateView()
}
fun setCurrentIndicateLineTextColor(currentIndicateLineTextColor: Int) {
mCurrentIndicateLineTextColor = currentIndicateLineTextColor
invalidateView()
}
fun setIndicatorLineColor(indicatorLineColor: Int) {
mIndicatorLineColor = indicatorLineColor
invalidateView()
}
fun setIndicatorMargin(indicatorMargin: Float) {
mIndicatorMargin = indicatorMargin
invalidateView()
}
fun setIconLineGap(iconLineGap: Float) {
mIconLineGap = iconLineGap
invalidateView()
}
fun setIconWidth(iconWidth: Float) {
mIconWidth = iconWidth
invalidateView()
}
fun setIconHeight(iconHeight: Float) {
mIconHeight = iconHeight
invalidateView()
}
fun setEnableShowIndicator(enableShowIndicator: Boolean) {
isEnableShowIndicator = enableShowIndicator
invalidateView()
}
fun setIndicatorTextColor(indicatorTextColor: Int) {
mIndicatorTextColor = indicatorTextColor
invalidateView()
}
interface OnPlayIndicatorLineListener {
fun onPlay(time: Long, content: String)
}
companion object {
private const val DEFAULT_CONTENT = "Empty"
}
}

View file

@ -22,7 +22,6 @@ import code.name.monkey.retromusic.ui.adapter.HomeAdapter.Companion.GENRES
import code.name.monkey.retromusic.ui.adapter.HomeAdapter.Companion.PLAYLISTS
import code.name.monkey.retromusic.ui.adapter.HomeAdapter.Companion.RECENT_ALBUMS
import code.name.monkey.retromusic.ui.adapter.HomeAdapter.Companion.RECENT_ARTISTS
import code.name.monkey.retromusic.ui.adapter.HomeAdapter.Companion.SUGGESTIONS
import code.name.monkey.retromusic.ui.adapter.HomeAdapter.Companion.TOP_ALBUMS
import code.name.monkey.retromusic.ui.adapter.HomeAdapter.Companion.TOP_ARTISTS
import code.name.monkey.retromusic.util.PreferenceUtil
@ -80,7 +79,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
disposable += repository.topAlbums
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
if (it.isNotEmpty()) hashSet.add(Home(2, R.string.top_albums, 0, it, TOP_ALBUMS, R.drawable.ic_album_white_24dp))
if (it.isNotEmpty()) hashSet.add(Home(3, R.string.top_albums, 0, it, TOP_ALBUMS, R.drawable.ic_album_white_24dp))
view.showData(ArrayList(hashSet))
}, {
view.showEmpty()
@ -91,7 +90,7 @@ class HomePresenter(private val view: HomeContract.HomeView) : Presenter(), Home
disposable += repository.topArtists
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
if (it.isNotEmpty()) hashSet.add(Home(3, R.string.top_artists, 0, it, TOP_ARTISTS, R.drawable.ic_artist_white_24dp))
if (it.isNotEmpty()) hashSet.add(Home(2, R.string.top_artists, 0, it, TOP_ARTISTS, R.drawable.ic_artist_white_24dp))
view.showData(ArrayList(hashSet))
}, {
view.showEmpty()

View file

@ -10,6 +10,8 @@ import android.widget.ArrayAdapter
import android.widget.SeekBar
import android.widget.TextView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
@ -24,17 +26,7 @@ import kotlinx.android.synthetic.main.activity_equalizer.*
class EqualizerActivity : AbsMusicServiceActivity(), AdapterView.OnItemSelectedListener {
/*private val mListener = { buttonView, isChecked ->
when (buttonView.getId()) {
R.id.equalizerSwitch -> {
EqualizerHelper.instance!!.equalizer.enabled = isChecked
TransitionManager.beginDelayedTransition(content)
content.visibility = if (isChecked) View.VISIBLE else View.GONE
}
}
}*/
private val mSeekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
private val seekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
if (seekBar === bassBoostStrength) {
@ -58,14 +50,12 @@ class EqualizerActivity : AbsMusicServiceActivity(), AdapterView.OnItemSelectedL
}
}
private var mPresetsNamesAdapter: ArrayAdapter<String>? = null
private var presetsNamesAdapter: ArrayAdapter<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_equalizer)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
@ -74,7 +64,10 @@ class EqualizerActivity : AbsMusicServiceActivity(), AdapterView.OnItemSelectedL
setupToolbar()
equalizerSwitch.isChecked = EqualizerHelper.instance!!.equalizer.enabled
TintHelper.setTintAuto(equalizerSwitch, ThemeStore.accentColor(this), false)
equalizerSwitch.setBackgroundColor(ThemeStore.accentColor(this))
val widgetColor = MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))
equalizerSwitch.setTextColor(widgetColor)
TintHelper.setTintAuto(equalizerSwitch, widgetColor, false)
equalizerSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
when (buttonView.id) {
R.id.equalizerSwitch -> {
@ -85,17 +78,17 @@ class EqualizerActivity : AbsMusicServiceActivity(), AdapterView.OnItemSelectedL
}
}
mPresetsNamesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
presets.adapter = mPresetsNamesAdapter
presetsNamesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
presets.adapter = presetsNamesAdapter
presets.onItemSelectedListener = this
bassBoostStrength.progress = EqualizerHelper.instance!!.bassBoostStrength
ViewUtil.setProgressDrawable(bassBoostStrength, ThemeStore.accentColor(this))
bassBoostStrength.setOnSeekBarChangeListener(mSeekBarChangeListener)
bassBoostStrength.setOnSeekBarChangeListener(seekBarChangeListener)
virtualizerStrength.progress = EqualizerHelper.instance!!.virtualizerStrength
ViewUtil.setProgressDrawable(virtualizerStrength, ThemeStore.accentColor(this))
virtualizerStrength.setOnSeekBarChangeListener(mSeekBarChangeListener)
virtualizerStrength.setOnSeekBarChangeListener(seekBarChangeListener)
setupUI()
addPresets()
@ -124,12 +117,12 @@ class EqualizerActivity : AbsMusicServiceActivity(), AdapterView.OnItemSelectedL
}
private fun addPresets() {
mPresetsNamesAdapter!!.clear()
mPresetsNamesAdapter!!.add("Custom")
presetsNamesAdapter!!.clear()
presetsNamesAdapter!!.add("Custom")
for (j in 0 until EqualizerHelper.instance!!.equalizer.numberOfPresets) {
mPresetsNamesAdapter!!
presetsNamesAdapter!!
.add(EqualizerHelper.instance!!.equalizer.getPresetName(j.toShort()))
mPresetsNamesAdapter!!.notifyDataSetChanged()
presetsNamesAdapter!!.notifyDataSetChanged()
}
presets.setSelection(EqualizerHelper.instance!!.equalizer.currentPreset.toInt() + 1)
}

View file

@ -22,6 +22,8 @@ import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.lyrics.LrcHelper
import code.name.monkey.retromusic.lyrics.LrcView
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.ui.activities.base.AbsMusicServiceActivity
@ -33,8 +35,6 @@ import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import com.lauzy.freedom.library.LrcHelper
import com.lauzy.freedom.library.LrcView
import kotlinx.android.synthetic.main.activity_lyrics.*
import kotlinx.android.synthetic.main.fragment_lyrics.*
import kotlinx.android.synthetic.main.fragment_synced.*

View file

@ -41,7 +41,6 @@ class HomeAdapter(private val activity: AppCompatActivity, private var homes: Li
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false)
return when (viewType) {
SUGGESTIONS -> SuggestionViewHolder(LayoutInflater.from(activity).inflate(R.layout.section_item_collage, parent, false))
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
GENRES -> GenreViewHolder(layout)
PLAYLISTS -> PlaylistViewHolder(layout)
@ -54,10 +53,7 @@ class HomeAdapter(private val activity: AppCompatActivity, private var homes: Li
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val home = homes[position]
when (getItemViewType(position)) {
SUGGESTIONS -> {
val viewHolder = holder as SuggestionViewHolder
viewHolder.bindView(home)
}
RECENT_ALBUMS, TOP_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home)
@ -88,17 +84,16 @@ class HomeAdapter(private val activity: AppCompatActivity, private var homes: Li
companion object {
@IntDef(SUGGESTIONS, RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, GENRES, PLAYLISTS)
@IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, GENRES, PLAYLISTS)
@Retention(AnnotationRetention.SOURCE)
annotation class HomeSection
const val SUGGESTIONS = 0
const val RECENT_ALBUMS = 1
const val TOP_ALBUMS = 2
const val RECENT_ARTISTS = 3
const val TOP_ARTISTS = 4
const val GENRES = 5
const val PLAYLISTS = 6
const val RECENT_ALBUMS = 0
const val TOP_ALBUMS = 1
const val RECENT_ARTISTS = 2
const val TOP_ARTISTS = 3
const val GENRES = 4
const val PLAYLISTS = 5
}

View file

@ -1,145 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util.color;
import android.content.Context;
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.util.Log;
import android.util.Pair;
import java.util.WeakHashMap;
import code.name.monkey.retromusic.util.ImageUtil;
/**
* 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));
}
}