diff --git a/app/build.gradle b/app/build.gradle index 11c7b2de..f3ef42da 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,7 +32,7 @@ android { vectorDrawables.useSupportLibrary = true applicationId "code.name.monkey.retromusic" - versionCode 308 + versionCode 311 versionName '3.1.300' multiDexEnabled true @@ -164,6 +164,7 @@ dependencies { implementation 'com.github.takahirom.downloadable.calligraphy:downloadable-calligraphy:0.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2' implementation project(':appthemehelper') + implementation project(':library') } repositories { mavenCentral() diff --git a/app/src/main/assets/retro-changelog.html b/app/src/main/assets/retro-changelog.html index d9967f9d..ce928b00 100644 --- a/app/src/main/assets/retro-changelog.html +++ b/app/src/main/assets/retro-changelog.html @@ -1 +1 @@ -

v3.1.300

v3.1.240

v3.1.200

v3.0.570

If you see entire app white or dark or black select same theme in settings to fix

FAQ's

*If you face any UI related issues you clear app data and cache, if its not working try to uninstall and install again.

\ No newline at end of file +

v3.1.300

v3.1.240

v3.1.200

v3.0.570

If you see entire app white or dark or black select same theme in settings to fix

FAQ's

*If you face any UI related issues you clear app data and cache, if its not working try to uninstall and install again.

\ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt index 9b59f661..a166cda8 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/CreatePlaylistDialog.kt @@ -14,7 +14,6 @@ package code.name.monkey.retromusic.dialogs -import android.content.Context import android.content.res.ColorStateList import android.os.Bundle import android.view.LayoutInflater @@ -22,28 +21,26 @@ import android.view.View import android.view.ViewGroup import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.util.MaterialUtil - import code.name.monkey.retromusic.R import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment import kotlinx.android.synthetic.main.dialog_playlist.* -import java.util.* class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_playlist, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val accentColor = ThemeStore.accentColor(Objects.requireNonNull(context)) - val songs = arguments!!.getParcelableArrayList("songs") + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + + val accentColor = ThemeStore.accentColor(context!!) MaterialUtil.setTint(actionCreate, true) MaterialUtil.setTint(actionCancel, false) @@ -51,17 +48,17 @@ class CreatePlaylistDialog : RoundedBottomSheetDialogFragment() { actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor)) actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!)) - bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + val songs = arguments!!.getParcelableArrayList("songs") + actionCancel.setOnClickListener { dismiss() } actionCreate.setOnClickListener { if (activity == null) { return@setOnClickListener } if (!actionNewPlaylist!!.text!!.toString().trim { it <= ' ' }.isEmpty()) { - val playlistId = PlaylistsUtil - .createPlaylist(activity!!, actionNewPlaylist!!.text!!.toString()) + val playlistId = PlaylistsUtil.createPlaylist(activity!!, actionNewPlaylist!!.text!!.toString()) if (playlistId != -1 && activity != null) { if (songs != null) { PlaylistsUtil.addToPlaylist(activity!!, songs, playlistId, true) diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt index 3a7c87b0..e6864d5c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeletePlaylistDialog.kt @@ -48,8 +48,8 @@ class DeletePlaylistDialog : RoundedBottomSheetDialogFragment() { } else { Html.fromHtml(getString(R.string.delete_playlist_x, playlists[0].name)) } - dialogTitle.text = content - dialogTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + bannerTitle.text = content + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) actionDelete.apply { setText(R.string.action_delete) diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt index 4cd0a45b..a424b674 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt @@ -34,7 +34,7 @@ class DeleteSongsDialog : RoundedBottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - dialogTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) //noinspection unchecked,ConstantConditions val songs = arguments!!.getParcelableArrayList("songs") val content: CharSequence @@ -44,7 +44,7 @@ class DeleteSongsDialog : RoundedBottomSheetDialogFragment() { } else { getString(R.string.delete_song_x, songs[0].title) } - dialogTitle.text = Html.fromHtml(content) + bannerTitle.text = Html.fromHtml(content) } actionDelete.apply { setOnClickListener { diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt index 6bef42fc..2a6e87bc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RemoveFromPlaylistDialog.kt @@ -27,35 +27,33 @@ import code.name.monkey.retromusic.model.PlaylistSong import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.views.RoundedBottomSheetDialogFragment -import kotlinx.android.synthetic.main.dialog_remove_from_playlist.* +import kotlinx.android.synthetic.main.dialog_delete.* + class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.dialog_remove_from_playlist, container, false) + return inflater.inflate(R.layout.dialog_delete, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val songs = arguments!!.getParcelableArrayList("songs") - val title: Int val content: CharSequence if (songs!!.size > 1) { - title = R.string.remove_songs_from_playlist_title content = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size), Html.FROM_HTML_MODE_LEGACY) } else { Html.fromHtml(getString(R.string.remove_x_songs_from_playlist, songs.size)) } } else { - title = R.string.remove_song_from_playlist_title content = Html.fromHtml(getString(R.string.remove_song_x_from_playlist, songs[0].title)) } bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) bannerTitle.text = content; actionDelete.apply { - setText(title) + setIconResource(R.drawable.ic_delete_white_24dp) + setText(R.string.remove_action) setTextColor(ThemeStore.textColorSecondary(context)) setOnClickListener { val playlistSongs = ArrayList() @@ -68,6 +66,7 @@ class RemoveFromPlaylistDialog : RoundedBottomSheetDialogFragment() { actionCancel.apply { + setIconResource(R.drawable.ic_close_white_24dp) setTextColor(ThemeStore.textColorSecondary(context)) setOnClickListener { dismiss() } MaterialUtil.setTint(this, false) diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt index 741a7345..50edcb02 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/RenamePlaylistDialog.kt @@ -36,10 +36,14 @@ class RenamePlaylistDialog : RoundedBottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val accentColor = ThemeStore.accentColor(context!!) + bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) + bannerTitle.setText(R.string.rename_playlist_title) MaterialUtil.setTint(actionNewPlaylistContainer, false) + val accentColor = ThemeStore.accentColor(context!!) + actionNewPlaylist.setHintTextColor(ColorStateList.valueOf(accentColor)) + actionNewPlaylist.setTextColor(ThemeStore.textColorPrimary(context!!)) actionNewPlaylist.apply { var playlistId: Long = 0 @@ -47,14 +51,10 @@ class RenamePlaylistDialog : RoundedBottomSheetDialogFragment() { playlistId = arguments!!.getLong("playlist_id") } setText(PlaylistsUtil.getNameForPlaylist(activity!!, playlistId)) - setHintTextColor(ColorStateList.valueOf(accentColor)) - setTextColor(ThemeStore.textColorPrimary(context!!)) } - bannerTitle.setTextColor(ThemeStore.textColorPrimary(context!!)) - bannerTitle.setText(R.string.rename_playlist_title) actionCancel.apply { - MaterialUtil.setTint(actionCancel, false) + MaterialUtil.setTint(this, false) setOnClickListener { dismiss() } icon = ContextCompat.getDrawable(context, R.drawable.ic_close_white_24dp) } diff --git a/app/src/main/java/code/name/monkey/retromusic/exfab/FabIconAnimator.java b/app/src/main/java/code/name/monkey/retromusic/exfab/FabIconAnimator.java deleted file mode 100644 index e3384af0..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/exfab/FabIconAnimator.java +++ /dev/null @@ -1,143 +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.exfab; - -/** - * Created by hemanths on 3/20/19 - */ - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.transition.AutoTransition; -import android.transition.Transition; -import android.transition.TransitionManager; -import android.view.View; - -import com.google.android.material.button.MaterialButton; - -import java.util.concurrent.atomic.AtomicBoolean; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintSet; -import code.name.monkey.retromusic.R; - -public class FabIconAnimator { - - private static final String ROTATION_Y_PROPERTY = "rotationY"; - - private static final float TWITCH_END = 20F; - private static final float TWITCH_START = 0F; - private static final int DURATION = 200; - private final MaterialButton button; - private final ConstraintLayout container; - @DrawableRes - private int currentIcon; - @StringRes - private int currentText; - private boolean isAnimating; - private final Transition.TransitionListener listener = new Transition.TransitionListener() { - public void onTransitionStart(Transition transition) { - isAnimating = true; - } - - public void onTransitionEnd(Transition transition) { - isAnimating = false; - } - - public void onTransitionCancel(Transition transition) { - isAnimating = false; - } - - public void onTransitionPause(Transition transition) { - } - - public void onTransitionResume(Transition transition) { - } - }; - - public FabIconAnimator(ConstraintLayout container) { - this.container = container; - this.button = container.findViewById(R.id.fab); - } - - public void update(@DrawableRes int icon, @StringRes int text) { - boolean isSame = currentIcon == icon && currentText == text; - currentIcon = icon; - currentText = text; - animateChange(icon, text, isSame); - } - - public void setOnClickListener(@Nullable View.OnClickListener clickListener) { - if (clickListener == null) { - button.setOnClickListener(null); - return; - } - AtomicBoolean flag = new AtomicBoolean(true); - button.setOnClickListener(view -> { - if (!flag.getAndSet(false)) return; - clickListener.onClick(view); - button.postDelayed(() -> flag.set(true), 2000); - }); - } - - private boolean isExtended() { // R.dimen.triple_and_half_margin is 56 dp. - return button.getLayoutParams().height != button.getResources().getDimensionPixelSize(R.dimen.triple_and_half_margin); - } - - public void setExtended(boolean extended) { - setExtended(extended, false); - } - - private void animateChange(@DrawableRes int icon, @StringRes int text, boolean isSame) { - boolean extended = isExtended(); - button.setText(text); - button.setIconResource(icon); - setExtended(extended, !isSame); - if (!extended) twitch(); - } - - private void setExtended(boolean extended, boolean force) { - if (isAnimating || (extended && isExtended() && !force)) return; - - ConstraintSet set = new ConstraintSet(); - set.clone(container.getContext(), extended ? R.layout.fab_extended : R.layout.fab_collapsed); - - TransitionManager.beginDelayedTransition(container, new AutoTransition() - .addListener(listener).setDuration(150)); - - if (extended) button.setText(currentText); - else button.setText(""); - - set.applyTo(container); - } - - private void twitch() { - AnimatorSet set = new AnimatorSet(); - ObjectAnimator twitchA = animateProperty(ROTATION_Y_PROPERTY, TWITCH_START, TWITCH_END); - ObjectAnimator twitchB = animateProperty(ROTATION_Y_PROPERTY, TWITCH_END, TWITCH_START); - - set.play(twitchB).after(twitchA); - set.start(); - } - - @NonNull - private ObjectAnimator animateProperty(String property, float start, float end) { - return ObjectAnimator.ofFloat(container, property, start, end).setDuration(DURATION); - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/exfab/TransientBarBehavior.java b/app/src/main/java/code/name/monkey/retromusic/exfab/TransientBarBehavior.java deleted file mode 100644 index 132ed5de..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/exfab/TransientBarBehavior.java +++ /dev/null @@ -1,75 +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.exfab; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.animation.Interpolator; - -import com.google.android.material.snackbar.Snackbar; - -import java.util.List; - -import androidx.annotation.NonNull; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.view.ViewCompat; -import androidx.interpolator.view.animation.FastOutSlowInInterpolator; - -public class TransientBarBehavior extends CoordinatorLayout.Behavior { - - private static final Interpolator fastOutSlowInInterpolator = new FastOutSlowInInterpolator(); - - public TransientBarBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) { - return dependency instanceof Snackbar.SnackbarLayout; - } - - @Override - public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) { - if (child.getVisibility() == View.VISIBLE) { - float translationY = this.getViewTranslationYForSnackbar(parent, child); - child.setTranslationY(translationY); - } - return true; - } - - @Override - public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) { - if (dependency instanceof Snackbar.SnackbarLayout && child.getTranslationY() != 0.0F) { - ViewCompat.animate(child).translationY(0.0F).scaleX(1.0F).scaleY(1.0F).alpha(1.0F) - .setInterpolator(fastOutSlowInInterpolator); - } - } - - private float getViewTranslationYForSnackbar(CoordinatorLayout parent, View child) { - float minOffset = 0.0F; - List dependencies = parent.getDependencies(child); - int i = 0; - - for (int z = dependencies.size(); i < z; ++i) { - View view = (View) dependencies.get(i); - if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view)) { - minOffset = Math.min(minOffset, view.getTranslationY() - (float) view.getHeight()); - } - } - - return minOffset; - } -} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt index fa8edde0..07a63a4c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/AlbumDetailsActivity.kt @@ -219,19 +219,20 @@ class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContrac }) return@map it.albums!! } - + .map { it.filter { albumSearch -> albumSearch.id != album.id } } .subscribe { - - it.remove(album) - if (!it.isEmpty()) { - moreTitle.visibility = View.VISIBLE - moreRecyclerView.visibility = View.VISIBLE - } else { + for (albumFinal in it) { + if (albumFinal.id == album.id) + println("$albumFinal -> $album") + } + if (it.isEmpty()) { return@subscribe } + moreTitle.visibility = View.VISIBLE + moreRecyclerView.visibility = View.VISIBLE moreTitle.text = String.format("More from %s", album.artistName) - val albumAdapter = HorizontalAlbumAdapter(this, it, false, null) + val albumAdapter = HorizontalAlbumAdapter(this, it as ArrayList, false, null) moreRecyclerView.layoutManager = GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false) moreRecyclerView.adapter = albumAdapter diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/LyricsActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LyricsActivity.kt index a95fcbf2..b06a407f 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/LyricsActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/LyricsActivity.kt @@ -1,6 +1,7 @@ package code.name.monkey.retromusic.ui.activities import android.annotation.SuppressLint +import android.content.res.ColorStateList import android.os.AsyncTask import android.os.Bundle import android.text.InputType @@ -12,6 +13,8 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter import androidx.viewpager.widget.ViewPager 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.retromusic.App import code.name.monkey.retromusic.R @@ -28,6 +31,8 @@ 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.* @@ -37,15 +42,22 @@ import java.util.* class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) { - + when (state) { + ViewPager.SCROLL_STATE_IDLE -> + fab.show(true) + ViewPager.SCROLL_STATE_DRAGGING, + ViewPager.SCROLL_STATE_SETTLING -> + fab.hide(true) + } } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - } override fun onPageSelected(position: Int) { PreferenceUtil.getInstance().lyricsOptions = position + if (position == 0) fab.text = "Sync lyrics" + else if (position == 1) fab.text = "Lyrics" } override fun onClick(v: View?) { @@ -83,7 +95,11 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage } - TintHelper.setTintAuto(fab, ThemeStore.accentColor(this), true) + fab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this)) + ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply { + fab.setTextColor(this) + fab.iconTint = this + } setupWakelock() viewPager.apply { @@ -258,8 +274,8 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage offlineLyrics?.setText(R.string.no_lyrics_found) return } - (activity as LyricsActivity).lyricsString = l.data - offlineLyrics?.text = l.data + (activity as LyricsActivity).lyricsString = l.text + offlineLyrics?.text = l.text } override fun onCancelled(s: Lyrics?) { @@ -302,11 +318,14 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage private fun setupLyricsView() { lyricsView.apply { - setOnPlayerClickListener { progress, _ -> MusicPlayerRemote.seekTo(progress.toInt()) } - setDefaultColor(ContextCompat.getColor(context, R.color.md_grey_400)) - setHintColor(ThemeStore.textColorPrimary(context)) - setHighLightColor(ThemeStore.textColorPrimary(context)) - setTextSize(RetroUtil.convertDpToPixel(18f, context).toInt()) + setCurrentPlayLineColor(ThemeStore.accentColor(context)) + setIndicatorTextColor(ThemeStore.accentColor(context)) + setCurrentIndicateLineTextColor(ThemeStore.textColorPrimary(context)) + setOnPlayIndicatorLineListener(object : LrcView.OnPlayIndicatorLineListener { + override fun onPlay(time: Long, content: String) { + MusicPlayerRemote.seekTo(time.toInt()) + } + }) } } @@ -321,7 +340,7 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage } override fun onUpdateProgressViews(progress: Int, total: Int) { - lyricsView.setCurrentTimeMillis(progress.toLong()) + lyricsView.updateTime(progress.toLong()) } private fun loadLRCLyrics() { @@ -332,10 +351,8 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPage } private fun showLyricsLocal(file: File?) { - if (file == null) { - lyricsView.reset() - } else { - lyricsView.setLyricFile(file, "UTF-8") + if (file != null) { + lyricsView.setLrcData(LrcHelper.parseLrcFromFile(file)) } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/UserInfoActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/UserInfoActivity.kt index 5132d8ae..359ca05c 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/UserInfoActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/UserInfoActivity.kt @@ -107,7 +107,7 @@ class UserInfoActivity : AbsBaseActivity() { toolbar.apply { setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp) setBackgroundColor(primaryColor) - ToolbarContentTintHelper.colorBackButton(this, ThemeStore.accentColor(this@UserInfoActivity)) + ToolbarContentTintHelper.colorBackButton(this, ThemeStore.textColorSecondary(this@UserInfoActivity)) setSupportActionBar(this) } appBarLayout.setBackgroundColor(primaryColor) diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/SongTagEditorActivity.kt index 81627036..4fbf4296 100755 --- a/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/SongTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/activities/tageditor/SongTagEditorActivity.kt @@ -44,7 +44,7 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { private fun setUpViews() { fillViewsWithFileTags() - MaterialUtil.setTint(songTextContainer) + MaterialUtil.setTint(songTextContainer,false) MaterialUtil.setTint(composerContainer, false) MaterialUtil.setTint(albumTextContainer, false) MaterialUtil.setTint(artistContainer, false) diff --git a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerFragment.kt b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerFragment.kt index 7197a39f..c01186de 100644 --- a/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/ui/fragments/player/normal/PlayerFragment.kt @@ -106,14 +106,14 @@ class PlayerFragment : AbsPlayerFragment(), PlayerAlbumCoverFragment.Callbacks { snowfall.visibility = if (PreferenceUtil.getInstance().isSnowFall) View.VISIBLE else View.GONE - val display = activity?.windowManager?.defaultDisplay - val outMetrics = DisplayMetrics() - display?.getMetrics(outMetrics) + //val display = activity?.windowManager?.defaultDisplay + //val outMetrics = DisplayMetrics() + //display?.getMetrics(outMetrics) - val density = resources.displayMetrics.density - val dpWidth = outMetrics.widthPixels / density + //val density = resources.displayMetrics.density + //val dpWidth = outMetrics.widthPixels / density - playerAlbumCoverContainer?.layoutParams?.height = RetroUtil.convertDpToPixel((dpWidth - getCutOff()), context!!).toInt() + //playerAlbumCoverContainer?.layoutParams?.height = RetroUtil.convertDpToPixel((dpWidth - getCutOff()), context!!).toInt() } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java index 20b4c1cd..00ab4dc1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java @@ -121,9 +121,7 @@ public class PlaylistsUtil { public static void addToPlaylist(@NonNull final Context context, @NonNull final List songs, final int playlistId, final boolean showToastOnFinish) { final int size = songs.size(); final ContentResolver resolver = context.getContentResolver(); - final String[] projection = new String[]{ - "max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")", - }; + final 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; diff --git a/app/src/main/java/code/name/monkey/retromusic/views/LyricView.java b/app/src/main/java/code/name/monkey/retromusic/views/LyricView.java deleted file mode 100644 index e83847ab..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/views/LyricView.java +++ /dev/null @@ -1,890 +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.views; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.os.Looper; -import android.text.Layout; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; - -import androidx.annotation.IntDef; -import androidx.core.content.res.ResourcesCompat; - -import org.mozilla.universalchardet.UniversalDetector; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; - -import code.name.monkey.retromusic.R; - -/** - * Created by zhengken.me on 2016/11/27. - * ClassName : LyricView - * Description : - */ -public class LyricView extends View { - - public static final int LEFT = 0; - public static final int CENTER = 1; - public static final int RIGHT = 2; - private static final String TAG = "LyricView"; - private static final float SLIDE_COEFFICIENT = 0.2f; - - private static final int UNITS_SECOND = 1000; - private static final int UNITS_MILLISECOND = 1; - - private static final int FLING_ANIMATOR_DURATION = 500 * UNITS_MILLISECOND; - - private static final int THRESHOLD_Y_VELOCITY = 1600; - - private static final int INDICATOR_ICON_PLAY_MARGIN_LEFT = 7;//dp - private static final int INDICATOR_ICON_PLAY_WIDTH = 15;//sp - private static final int INDICATOR_LINE_MARGIN = 10;//dp - private static final int INDICATOR_TIME_TEXT_SIZE = 10;//sp - private static final int INDICATOR_TIME_MARGIN_RIGHT = 7;//dp - - private static final int DEFAULT_TEXT_SIZE = 16;//sp - private static final int DEFAULT_MAX_LENGTH = 300;//dp - private static final int DEFAULT_LINE_SPACE = 25;//dp - - private int mHintColor; - private int mDefaultColor; - private int mHighLightColor; - private int mTextAlign; - - - private int mLineCount; - private int mTextSize; - private float mLineHeight; - private LyricInfo mLyricInfo; - private String mDefaultHint; - private int mMaxLength; - - private TextPaint mTextPaint; - private Paint mBtnPlayPaint; - private Paint mLinePaint; - private Paint mTimerPaint; - - private boolean mFling = false; - private ValueAnimator mFlingAnimator; - private float mScrollY = 0; - private float mLineSpace = 0; - private boolean mIsShade; - private float mShaderWidth = 0; - private int mCurrentPlayLine = 0; - private boolean mShowIndicator; - - private VelocityTracker mVelocityTracker; - private float mVelocity = 0; - private float mDownX; - private float mDownY; - private float mLastScrollY; - private boolean mUserTouch = false; - Runnable hideIndicator = () -> { - setUserTouch(false); - invalidateView(); - }; - private int maxVelocity; - private int mLineNumberUnderIndicator = 0; - private Rect mBtnPlayRect = new Rect(); - private Rect mTimerRect; - private String mDefaultTime = "00:00"; - private int mLineColor = Color.parseColor("#EFEFEF"); - private int mBtnColor = Color.parseColor("#EFEFEF"); - private List mLineFeedRecord = new ArrayList<>(); - private boolean mEnableLineFeed = false; - private int mExtraHeight = 0; - private int mTextHeight; - private String mCurrentLyricFilePath = null; - private OnPlayerClickListener mClickListener; - - public LyricView(Context context) { - super(context); - initMyView(context); - } - - public LyricView(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - getAttrs(context, attributeSet); - initMyView(context); - - } - - public LyricView(Context context, AttributeSet attributeSet, int i) { - super(context, attributeSet, i); - getAttrs(context, attributeSet); - initMyView(context); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - return super.dispatchTouchEvent(event); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - switch (event.getAction()) { - case MotionEvent.ACTION_CANCEL: - actionCancel(event); - break; - case MotionEvent.ACTION_DOWN: - actionDown(event); - break; - case MotionEvent.ACTION_MOVE: - actionMove(event); - break; - case MotionEvent.ACTION_UP: - actionUp(event); - break; - default: - break; - } - invalidateView(); - return true; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mBtnPlayRect.set((int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT), - (int) (getHeight() * 0.5f - getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f), - (int) (getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_ICON_PLAY_MARGIN_LEFT)), - (int) (getHeight() * 0.5f + getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_ICON_PLAY_WIDTH) * 0.5f)); - mShaderWidth = getWidth() * 0.4f; - } - - @Override - protected void onDraw(Canvas canvas) { - if (scrollable()) { - if (mShowIndicator) { - drawIndicator(canvas); - } - - for (int i = 0; i < mLineCount; i++) { - float x = 0; - switch (mTextAlign) { - case LEFT: - x = INDICATOR_ICON_PLAY_MARGIN_LEFT + INDICATOR_LINE_MARGIN + mBtnPlayRect.width(); - break; - case CENTER: - x = getWidth() * 0.5f; - break; - case RIGHT: - x = getWidth() - INDICATOR_LINE_MARGIN * 2 - mTimerRect.width() - INDICATOR_ICON_PLAY_MARGIN_LEFT; - break; - } - - float y; - if (mEnableLineFeed && i > 0) { - y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY + mLineFeedRecord.get(i - 1); - } else { - y = getMeasuredHeight() * 0.5f + i * mLineHeight - mScrollY; - } - -// float y = getHeight() * 0.5f + i * mLineHeight - mScrollY; - if (y < 0) { - continue; - } - if (y > getHeight()) { - break; - } - if (i == mCurrentPlayLine - 1) { - mTextPaint.setColor(mHighLightColor); - } else if (i == mLineNumberUnderIndicator && mShowIndicator) { - mTextPaint.setColor(Color.LTGRAY); - } else { - mTextPaint.setColor(mDefaultColor); - } - if (mIsShade && (y > getHeight() - mShaderWidth || y < mShaderWidth)) { - if (y < mShaderWidth) { - mTextPaint.setAlpha(26 + (int) (23000.0f * y / mShaderWidth * 0.01f)); - } else { - mTextPaint.setAlpha(26 + (int) (23000.0f * (getHeight() - y) / mShaderWidth * 0.01f)); - } - } else { - mTextPaint.setAlpha(255); - } - - if (mEnableLineFeed) { - StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint, - mMaxLength, - Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - canvas.save(); - canvas.translate(x, y); - staticLayout.draw(canvas); - canvas.restore(); - } else { - canvas.drawText(mLyricInfo.songLines.get(i).content, x, y, mTextPaint); - } - } - } else { - mTextPaint.setColor(mHintColor); - canvas.drawText(mDefaultHint, getMeasuredWidth() / 2, getMeasuredHeight() / 2, mTextPaint); - } - } - - private void getAttrs(Context context, AttributeSet attrs) { - TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LyricView); - mIsShade = ta.getBoolean(R.styleable.LyricView_fadeInFadeOut, false); - mDefaultHint = ta.getString(R.styleable.LyricView_hint) != null - ? ta.getString(R.styleable.LyricView_hint) - : getResources().getString(R.string.default_hint); - mHintColor = ta.getColor(R.styleable.LyricView_hintColor, Color.parseColor("#FFFFFF")); - mDefaultColor = ta.getColor(R.styleable.LyricView_textColor, Color.parseColor("#8D8D8D")); - mHighLightColor = ta.getColor(R.styleable.LyricView_highlightColor, Color.parseColor("#FFFFFF")); - mTextSize = ta.getDimensionPixelSize(R.styleable.LyricView_textSize, (int) getRawSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE)); - mTextAlign = ta.getInt(R.styleable.LyricView_textAlign, CENTER); - mMaxLength = ta.getDimensionPixelSize(R.styleable.LyricView_maxLength, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH)); - mLineSpace = ta.getDimensionPixelSize(R.styleable.LyricView_lineSpace, (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_LINE_SPACE)); - ta.recycle(); - } - - public void setShade(boolean shade) { - mIsShade = shade; - invalidateView(); - } - - public void setHintColor(int hintColor) { - mHintColor = hintColor; - invalidateView(); - } - - public void setDefaultColor(int defaultColor) { - mDefaultColor = defaultColor; - invalidateView(); - } - - public void setHighLightColor(int highLightColor) { - mHighLightColor = highLightColor; - invalidateView(); - } - - public void setTextAlign(int textAlign) { - mTextAlign = textAlign; - invalidateView(); - } - - public void setLineCount(int lineCount) { - mLineCount = lineCount; - invalidateView(); - } - - public void setTextSize(int textSize) { - mTextSize = textSize; - invalidateView(); - } - - public void setDefaultHint(String defaultHint) { - mDefaultHint = defaultHint; - invalidateView(); - } - - public void setMaxLength(int maxLength) { - mMaxLength = maxLength; - invalidateView(); - } - - public void setOnPlayerClickListener(OnPlayerClickListener mClickListener) { - this.mClickListener = mClickListener; - } - - public void setAlignment(@Alignment int alignment) { - mTextAlign = alignment; - } - - public void setCurrentTimeMillis(long current) { - scrollToCurrentTimeMillis(current); - } - - public void setLyricFile(File file) { - - if (file == null || !file.exists()) { - reset(); - mCurrentLyricFilePath = ""; - return; - } else if (file.getPath().equals(mCurrentLyricFilePath)) { - return; - } else { - mCurrentLyricFilePath = file.getPath(); - reset(); - } - try { - - FileInputStream fis = new FileInputStream(file); - byte[] buf = new byte[1024]; - UniversalDetector detector = new UniversalDetector(null); - int nread; - while ((nread = fis.read(buf)) > 0 && !detector.isDone()) { - detector.handleData(buf, 0, nread); - } - - detector.dataEnd(); - String encoding = detector.getDetectedCharset(); - if (encoding != null) { - setLyricFile(file, encoding); - } else { - setLyricFile(file, "UTF-8"); - } - detector.reset(); - fis.close(); - - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void setLyricFile(File file, String charsetName) { - if (file != null && file.exists()) { - try { - setupLyricResource(new FileInputStream(file), charsetName); - - for (int i = 0; i < mLyricInfo.songLines.size(); i++) { - - StaticLayout staticLayout = new StaticLayout(mLyricInfo.songLines.get(i).content, mTextPaint, - (int) getRawSize(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_MAX_LENGTH), - Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - - if (staticLayout.getLineCount() > 1) { - mEnableLineFeed = true; - mExtraHeight = mExtraHeight + (staticLayout.getLineCount() - 1) * mTextHeight; - } - - mLineFeedRecord.add(i, mExtraHeight); - - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } else { - invalidateView(); - } - } - - private void setLineSpace(float lineSpace) { - if (mLineSpace != lineSpace) { - mLineSpace = getRawSize(TypedValue.COMPLEX_UNIT_DIP, lineSpace); - measureLineHeight(); - mScrollY = measureCurrentScrollY(mCurrentPlayLine); - invalidateView(); - } - } - - public void reset() { - resetView(); - } - - private void actionCancel(MotionEvent event) { - releaseVelocityTracker(); - } - - private void actionDown(MotionEvent event) { - removeCallbacks(hideIndicator); - mLastScrollY = mScrollY; - mDownX = event.getX(); - mDownY = event.getY(); - if (mFlingAnimator != null) { - mFlingAnimator.cancel(); - mFlingAnimator = null; - } - setUserTouch(true); - } - - private boolean overScrolled() { - - return scrollable() && (mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0) || mScrollY < 0); - } - - private void actionMove(MotionEvent event) { - if (scrollable()) { - final VelocityTracker tracker = mVelocityTracker; - tracker.computeCurrentVelocity(UNITS_SECOND, maxVelocity); - mScrollY = mLastScrollY + mDownY - event.getY(); - mVelocity = tracker.getYVelocity(); - measureCurrentLine(); - } - } - - private void actionUp(MotionEvent event) { - - postDelayed(hideIndicator, 3 * UNITS_SECOND); - - releaseVelocityTracker(); - - if (scrollable()) { - if (overScrolled() && mScrollY < 0) { - smoothScrollTo(0); - return; - } - if (overScrolled() && mScrollY > mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)) { - smoothScrollTo(mLineHeight * (mLineCount - 1) + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)); - return; - } - if (Math.abs(mVelocity) > THRESHOLD_Y_VELOCITY) { - doFlingAnimator(mVelocity); - return; - } - if (mShowIndicator && clickPlayer(event)) { - if (mLineNumberUnderIndicator != mCurrentPlayLine) { - mShowIndicator = false; - if (mClickListener != null) { - setUserTouch(false); - mClickListener.onPlayerClicked(mLyricInfo.songLines.get(mLineNumberUnderIndicator).start, mLyricInfo.songLines.get(mLineNumberUnderIndicator).content); - } - } - } - } - } - - private String measureCurrentTime() { - DecimalFormat format = new DecimalFormat("00"); - if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 < mLineCount && mLineNumberUnderIndicator > 0) { - return format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineNumberUnderIndicator - 1).start / 1000 % 60); - } - if (mLyricInfo != null && mLineCount > 0 && (mLineNumberUnderIndicator - 1) >= mLineCount) { - return format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(mLineCount - 1).start / 1000 % 60); - } - if (mLyricInfo != null && mLineCount > 0 && mLineNumberUnderIndicator - 1 <= 0) { - return format.format(mLyricInfo.songLines.get(0).start / 1000 / 60) + ":" + format.format(mLyricInfo.songLines.get(0).start / 1000 % 60); - } - return mDefaultTime; - } - - private void drawIndicator(Canvas canvas) { - - //绘制 播放 按钮 - Path pathPlay = new Path(); - float yCoordinate = mBtnPlayRect.left + (float) Math.sqrt(Math.pow(mBtnPlayRect.width(), 2) - Math.pow(mBtnPlayRect.width() * 0.5f, 2)); - float remainWidth = mBtnPlayRect.right - yCoordinate; - - pathPlay.moveTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f); - pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() + mBtnPlayRect.height() * 0.5f); - pathPlay.lineTo(yCoordinate, mBtnPlayRect.centerY()); - pathPlay.lineTo(mBtnPlayRect.centerX() - mBtnPlayRect.width() * 0.5f, mBtnPlayRect.centerY() - mBtnPlayRect.height() * 0.5f); - - canvas.drawPath(pathPlay, mBtnPlayPaint); - - //绘制 分割线 - Path pathLine = new Path(); - pathLine.moveTo(mBtnPlayRect.right + getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN) - remainWidth, getMeasuredHeight() * 0.5f); - pathLine.lineTo(getWidth() - mTimerRect.width() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT) - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_LINE_MARGIN), getHeight() * 0.5f); - canvas.drawPath(pathLine, mLinePaint); - - //绘制 时间 - canvas.drawText(measureCurrentTime(), getWidth() - getRawSize(TypedValue.COMPLEX_UNIT_DIP, INDICATOR_TIME_MARGIN_RIGHT), (getHeight() + mTimerRect.height()) * 0.5f, mTimerPaint); - } - - private boolean clickPlayer(MotionEvent event) { - if (mBtnPlayRect != null && mDownX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && mDownY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT)) { - float upX = event.getX(); - float upY = event.getY(); - return upX > (mBtnPlayRect.left - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upX < (mBtnPlayRect.right + INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY > (mBtnPlayRect.top - INDICATOR_ICON_PLAY_MARGIN_LEFT) && upY < (mBtnPlayRect.bottom + INDICATOR_ICON_PLAY_MARGIN_LEFT); - } - return false; - } - - private void doFlingAnimator(float velocity) { - - float distance = (velocity / Math.abs(velocity) * (Math.abs(velocity) * SLIDE_COEFFICIENT)); - float to = Math.min(Math.max(0, (mScrollY - distance)), (mLineCount - 1) * mLineHeight + mLineFeedRecord.get(mLineCount - 1) + (mEnableLineFeed ? mTextHeight : 0)); - - mFlingAnimator = ValueAnimator.ofFloat(mScrollY, to); - mFlingAnimator.addUpdateListener(animation -> { - mScrollY = (float) animation.getAnimatedValue(); - measureCurrentLine(); - invalidateView(); - }); - - mFlingAnimator.addListener(new AnimatorListenerAdapter() { - - @Override - public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); - mVelocity = 0; - mFling = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mFling = false; - } - - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - } - }); - - mFlingAnimator.setDuration(FLING_ANIMATOR_DURATION); - mFlingAnimator.setInterpolator(new DecelerateInterpolator()); - mFlingAnimator.start(); - } - - private void setUserTouch(boolean isUserTouch) { - if (isUserTouch) { - mUserTouch = true; - mShowIndicator = true; - } else { - mUserTouch = false; - mShowIndicator = false; - } - } - - private void releaseVelocityTracker() { - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - private void initMyView(Context context) { - maxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity(); - initPaint(); - initAllBounds(); - } - - private void initAllBounds() { - setRawTextSize(mTextSize); - - setLineSpace(mLineSpace); - measureLineHeight(); - - mTimerRect = new Rect(); - mTimerPaint.getTextBounds(mDefaultTime, 0, mDefaultTime.length(), mTimerRect); - - - } - - private void initPaint() { - mTextPaint = new TextPaint(); - mTextPaint.setDither(true); - mTextPaint.setAntiAlias(true); - - switch (mTextAlign) { - case LEFT: - mTextPaint.setTextAlign(Paint.Align.LEFT); - break; - case CENTER: - mTextPaint.setTextAlign(Paint.Align.CENTER); - break; - case RIGHT: - mTextPaint.setTextAlign(Paint.Align.RIGHT); - break; - } - - mBtnPlayPaint = new Paint(); - mBtnPlayPaint.setDither(true); - mBtnPlayPaint.setAntiAlias(true); - mBtnPlayPaint.setColor(mBtnColor); - mBtnPlayPaint.setStyle(Paint.Style.FILL_AND_STROKE); - mBtnPlayPaint.setAlpha(128); - - mLinePaint = new Paint(); - mLinePaint.setDither(true); - mLinePaint.setAntiAlias(true); - mLinePaint.setColor(mLineColor); - mLinePaint.setAlpha(64); - mLinePaint.setStrokeWidth(1.0f); - mLinePaint.setStyle(Paint.Style.STROKE); - - mTimerPaint = new Paint(); - mTimerPaint.setDither(true); - mTimerPaint.setAntiAlias(true); - mTimerPaint.setColor(Color.WHITE); - mTimerPaint.setTextAlign(Paint.Align.RIGHT); - mTimerPaint.setTextSize(getRawSize(TypedValue.COMPLEX_UNIT_SP, INDICATOR_TIME_TEXT_SIZE)); - - - } - - private float measureCurrentScrollY(int line) { - if (mEnableLineFeed && line > 1) { - return (line - 1) * mLineHeight + mLineFeedRecord.get(line - 1); - } - return (line - 1) * mLineHeight; - } - - private void invalidateView() { - if (Looper.getMainLooper() == Looper.myLooper()) { - // 当前线程是主UI线程,直接刷新。 - invalidate(); - } else { - // 当前线程是非UI线程,post刷新。 - postInvalidate(); - } - } - - private void measureLineHeight() { - Rect lineBound = new Rect(); - mTextPaint.getTextBounds(mDefaultHint, 0, mDefaultHint.length(), lineBound); - mTextHeight = lineBound.height(); - mLineHeight = mTextHeight + mLineSpace; - } - - /** - * To measure current showing line number based on the view's scroll Y - */ - private void measureCurrentLine() { - float baseScrollY = mScrollY + mLineHeight * 0.5f; - - if (mEnableLineFeed) { - for (int i = mLyricInfo.songLines.size(); i >= 0; i--) { - if (baseScrollY > measureCurrentScrollY(i) + mLineSpace * 0.2) { - mLineNumberUnderIndicator = i - 1; - break; - } - } - } else { - mLineNumberUnderIndicator = (int) (baseScrollY / mLineHeight); - } - - - } - - private void smoothScrollTo(float toY) { - final ValueAnimator animator = ValueAnimator.ofFloat(mScrollY, toY); - animator.addUpdateListener(valueAnimator -> { - mScrollY = (Float) valueAnimator.getAnimatedValue(); - invalidateView(); - }); - - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationCancel(Animator animator) { - } - - @Override - public void onAnimationEnd(Animator animator) { - mFling = false; - measureCurrentLine(); - invalidateView(); - } - - @Override - public void onAnimationRepeat(Animator animator) { - } - - @Override - public void onAnimationStart(Animator animator) { - mFling = true; - } - }); - animator.setDuration(640); - animator.setInterpolator(new LinearInterpolator()); - - animator.start(); - } - - private boolean scrollable() { - return mLyricInfo != null && mLyricInfo.songLines != null && mLyricInfo.songLines.size() > 0; - } - - private void scrollToCurrentTimeMillis(long time) { - - int position = 0; - if (scrollable()) { - for (int i = 0, size = mLineCount; i < size; i++) { - LineInfo lineInfo = mLyricInfo.songLines.get(i); - if (lineInfo != null && lineInfo.start >= time) { - position = i; - break; - } - if (i == mLineCount - 1) { - position = mLineCount; - } - } - } - if (mCurrentPlayLine != position) { - mCurrentPlayLine = position; - if (!mFling && !mUserTouch) { - smoothScrollTo(measureCurrentScrollY(position)); - } - } - } - - private void setupLyricResource(InputStream inputStream, String charsetName) { - if (inputStream != null) { - try { - LyricInfo lyricInfo = new LyricInfo(); - lyricInfo.songLines = new ArrayList<>(); - InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName); - BufferedReader reader = new BufferedReader(inputStreamReader); - String line; - while ((line = reader.readLine()) != null) { - analyzeLyric(lyricInfo, line); - } - reader.close(); - inputStream.close(); - inputStreamReader.close(); - - mLyricInfo = lyricInfo; - mLineCount = mLyricInfo.songLines.size(); - invalidateView(); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - invalidateView(); - } - } - - /** - * 逐行解析歌词内容 - */ - private void analyzeLyric(LyricInfo lyricInfo, String line) { - int index = line.lastIndexOf("]"); - if (line.startsWith("[offset:")) { - // time offset - lyricInfo.songOffset = Long.parseLong(line.substring(8, index).trim()); - return; - } - if (line.startsWith("[ti:")) { - // title - lyricInfo.songTitle = line.substring(4, index).trim(); - return; - } - if (line.startsWith("[ar:")) { - // artist - lyricInfo.songArtist = line.substring(4, index).trim(); - return; - } - if (line.startsWith("[al:")) { - // album - lyricInfo.songAlbum = line.substring(4, index).trim(); - return; - } - if (line.startsWith("[by:")) { - return; - } - if (index >= 9 && line.trim().length() > index + 1) { - // lyrics - LineInfo lineInfo = new LineInfo(); - lineInfo.content = line.substring(10, line.length()); - lineInfo.start = measureStartTimeMillis(line.substring(0, index)); - lyricInfo.songLines.add(lineInfo); - } - } - - /** - * 从字符串中获得时间值 - */ - private long measureStartTimeMillis(String str) { - long minute = Long.parseLong(str.substring(1, 3)); - long second = Long.parseLong(str.substring(4, 6)); - long millisecond = Long.parseLong(str.substring(7, 9)); - return millisecond + second * 1000 + minute * 60 * 1000; - } - - private void resetLyricInfo() { - if (mLyricInfo != null) { - if (mLyricInfo.songLines != null) { - mLyricInfo.songLines.clear(); - mLyricInfo.songLines = null; - } - mLyricInfo = null; - } - } - - private void resetView() { - mCurrentPlayLine = 0; - resetLyricInfo(); - invalidateView(); - mLineCount = 0; - mScrollY = 0; - mEnableLineFeed = false; - mLineFeedRecord.clear(); - mExtraHeight = 0; - } - - private float getRawSize(int unit, float size) { - Context context = getContext(); - Resources resources; - if (context == null) { - resources = Resources.getSystem(); - } else { - resources = context.getResources(); - } - return TypedValue.applyDimension(unit, size, resources.getDisplayMetrics()); - } - - private void setRawTextSize(float size) { - if (size != mTextPaint.getTextSize()) { - mTextPaint.setTextSize(size); - measureLineHeight(); - mScrollY = measureCurrentScrollY(mCurrentPlayLine); - invalidateView(); - } - } - - @IntDef({LEFT, CENTER, RIGHT}) - @Retention(RetentionPolicy.SOURCE) - public @interface Alignment { - } - - public interface OnPlayerClickListener { - void onPlayerClicked(long progress, String content); - } - - private class LyricInfo { - List songLines; - - String songArtist; - String songTitle; - String songAlbum; - - long songOffset; - } - - private class LineInfo { - String content; - long start; - } -} \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_album_tag_editor.xml b/app/src/main/res/layout-land/activity_album_tag_editor.xml index 1cf47696..a96e211f 100644 --- a/app/src/main/res/layout-land/activity_album_tag_editor.xml +++ b/app/src/main/res/layout-land/activity_album_tag_editor.xml @@ -79,7 +79,7 @@ android:id="@+id/albumTitleContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - app:boxBackgroundMode="filled" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" app:hintEnabled="true"> - + android:text="@string/edit" + app:icon="@drawable/ic_edit_white_24dp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_song_tag_editor.xml b/app/src/main/res/layout/activity_song_tag_editor.xml index 1e2437b3..08659be6 100755 --- a/app/src/main/res/layout/activity_song_tag_editor.xml +++ b/app/src/main/res/layout/activity_song_tag_editor.xml @@ -69,12 +69,11 @@ + android:layout_height="wrap_content"> - @@ -144,10 +143,10 @@ @@ -192,12 +191,12 @@ @@ -216,10 +215,10 @@ @@ -237,10 +236,10 @@ diff --git a/app/src/main/res/layout/activity_user_info.xml b/app/src/main/res/layout/activity_user_info.xml index 2ff57181..4773c572 100644 --- a/app/src/main/res/layout/activity_user_info.xml +++ b/app/src/main/res/layout/activity_user_info.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical"> + app:hintAnimationEnabled="true"> + app:hintAnimationEnabled="true"> + android:orientation="vertical" + android:paddingBottom="16dp"> - + android:gravity="end" + android:orientation="horizontal"> - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_playlist.xml b/app/src/main/res/layout/dialog_playlist.xml index bffbbd38..3e053b00 100644 --- a/app/src/main/res/layout/dialog_playlist.xml +++ b/app/src/main/res/layout/dialog_playlist.xml @@ -16,18 +16,11 @@ - - - + android:gravity="end" + android:orientation="horizontal"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml index 950cbffc..02be2931 100644 --- a/app/src/main/res/layout/fragment_player.xml +++ b/app/src/main/res/layout/fragment_player.xml @@ -19,7 +19,7 @@ - @@ -30,6 +30,13 @@ android:orientation="vertical"> + + + + + - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_synced.xml b/app/src/main/res/layout/fragment_synced.xml index 00fae9f4..617f838b 100644 --- a/app/src/main/res/layout/fragment_synced.xml +++ b/app/src/main/res/layout/fragment_synced.xml @@ -6,15 +6,8 @@ android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + android:layout_height="match_parent" /> \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 9e80d635..da64b5fc 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -24,21 +24,6 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4327aa9..7e369d2e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -606,5 +606,6 @@ Save Pick image Set a profile photo + Edit diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index 0d6c4aef..373f5475 100755 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -19,7 +19,8 @@ android:persistent="false" android:summary="@string/primary_color_desc" android:title="@string/primary_color" - app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" + app:isPreferenceVisible="false" /> Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.lauzy.freedom.library.test", appContext.getPackageName()); + } +} diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9b5ce4b3 --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/library/src/main/java/com/lauzy/freedom/library/Lrc.java b/library/src/main/java/com/lauzy/freedom/library/Lrc.java new file mode 100644 index 00000000..c4dd229a --- /dev/null +++ b/library/src/main/java/com/lauzy/freedom/library/Lrc.java @@ -0,0 +1,30 @@ +package com.lauzy.freedom.library; + +/** + * 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 void setTime(long time) { + this.time = time; + } + + public void setText(String text) { + this.text = text; + } + + public long getTime() { + return time; + } + + public String getText() { + return text; + } + +} \ No newline at end of file diff --git a/library/src/main/java/com/lauzy/freedom/library/LrcHelper.java b/library/src/main/java/com/lauzy/freedom/library/LrcHelper.java new file mode 100644 index 00000000..b2eae73e --- /dev/null +++ b/library/src/main/java/com/lauzy/freedom/library/LrcHelper.java @@ -0,0 +1,137 @@ +package com.lauzy.freedom.library; + +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 parseLrcFromAssets(Context context, String fileName) { + try { + return parseInputStream(context.getResources().getAssets().open(fileName)); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static List parseLrcFromFile(File file) { + try { + return parseInputStream(new FileInputStream(file)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + private static List parseInputStream(InputStream inputStream) { + List 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 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 lrcs) { + Collections.sort(lrcs, new Comparator() { + @Override + public int compare(Lrc o1, Lrc o2) { + return (int) (o1.getTime() - o2.getTime()); + } + }); + } + + private static List parseLrc(String lrcLine) { + if (lrcLine.trim().isEmpty()) { + return null; + } + List 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 + ""; + } +} \ No newline at end of file diff --git a/library/src/main/java/com/lauzy/freedom/library/LrcView.kt b/library/src/main/java/com/lauzy/freedom/library/LrcView.kt new file mode 100644 index 00000000..d6d11449 --- /dev/null +++ b/library/src/main/java/com/lauzy/freedom/library/LrcView.kt @@ -0,0 +1,620 @@ +package com.lauzy.freedom.library + +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 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? = 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() + private val mHideIndicatorRunnable = Runnable { + isShowTimeIndicator = false + invalidateView() + } + private val mStaticLayoutHashMap = HashMap() + 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) { + 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" + } +} \ No newline at end of file diff --git a/library/src/main/res/drawable-xhdpi/play_icon.png b/library/src/main/res/drawable-xhdpi/play_icon.png new file mode 100644 index 00000000..b8b879c6 Binary files /dev/null and b/library/src/main/res/drawable-xhdpi/play_icon.png differ diff --git a/library/src/main/res/font/circular.xml b/library/src/main/res/font/circular.xml new file mode 100644 index 00000000..6f20e75b --- /dev/null +++ b/library/src/main/res/font/circular.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/library/src/main/res/font/circular_std_black.otf b/library/src/main/res/font/circular_std_black.otf new file mode 100755 index 00000000..c62b210c Binary files /dev/null and b/library/src/main/res/font/circular_std_black.otf differ diff --git a/library/src/main/res/font/circular_std_book.otf b/library/src/main/res/font/circular_std_book.otf new file mode 100755 index 00000000..3a1f1ad8 Binary files /dev/null and b/library/src/main/res/font/circular_std_book.otf differ diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml new file mode 100644 index 00000000..1b45e273 --- /dev/null +++ b/library/src/main/res/values/attrs.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml new file mode 100644 index 00000000..49fc91e1 --- /dev/null +++ b/library/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + library + diff --git a/library/src/test/java/com/lauzy/freedom/library/ExampleUnitTest.java b/library/src/test/java/com/lauzy/freedom/library/ExampleUnitTest.java new file mode 100644 index 00000000..94a3670b --- /dev/null +++ b/library/src/test/java/com/lauzy/freedom/library/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.lauzy.freedom.library; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 91441848..8a3bd3f4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':appthemehelper' \ No newline at end of file +include ':app', ':appthemehelper', ':library' \ No newline at end of file