This commit is contained in:
Muntashir Al-Islam 2020-07-14 13:54:52 +06:00
commit 51cdc2e9eb
50 changed files with 1754 additions and 1472 deletions

View file

@ -100,9 +100,9 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha06' implementation 'com.google.android.material:material:1.3.0-alpha01'
def retrofit_version = '2.8.1' def retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
@ -137,10 +137,6 @@ dependencies {
implementation 'com.heinrichreimersoftware:material-intro:1.6' implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0' implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
def dagger_version = '2.27'
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
def lifecycle_version = "2.2.0" def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

View file

@ -18,16 +18,12 @@ import androidx.multidex.MultiDexApplication
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.appthemehelper.util.VersionUtils
import io.github.muntashirakon.music.appshortcuts.DynamicShortcutManager import io.github.muntashirakon.music.appshortcuts.DynamicShortcutManager
import io.github.muntashirakon.music.dagger.DaggerMusicComponent
import io.github.muntashirakon.music.dagger.MusicComponent
import io.github.muntashirakon.music.dagger.module.AppModule
class App : MultiDexApplication() { class App : MultiDexApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
instance = this instance = this
musicComponent = initDagger(this)
// default theme // default theme
if (!ThemeStore.isConfigured(this, 3)) { if (!ThemeStore.isConfigured(this, 3)) {
@ -41,18 +37,11 @@ class App : MultiDexApplication() {
DynamicShortcutManager(this).initDynamicShortcuts() DynamicShortcutManager(this).initDynamicShortcuts()
} }
private fun initDagger(app: App): MusicComponent =
DaggerMusicComponent.builder()
.appModule(AppModule(app))
.build()
companion object { companion object {
private var instance: App? = null private var instance: App? = null
fun getContext(): App { fun getContext(): App {
return instance!! return instance!!
} }
lateinit var musicComponent: MusicComponent
} }
} }

View file

@ -18,10 +18,10 @@ import android.provider.BaseColumns
import android.provider.MediaStore import android.provider.MediaStore
object Constants { object Constants {
const val PRO_VERSION_PRODUCT_ID = "pro_version"
const val RATE_ON_GOOGLE_PLAY = const val RATE_ON_GOOGLE_PLAY =
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic" "https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
const val TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534" const val TRANSLATE = "https://github.com/h4h13/RetroMusicPlayer"
const val GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicPlayer" const val GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicPlayer"
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog" const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
const val USER_PROFILE = "profile.jpg" const val USER_PROFILE = "profile.jpg"
@ -116,6 +116,7 @@ const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
const val LAST_CHANGELOG_VERSION = "last_changelog_version" const val LAST_CHANGELOG_VERSION = "last_changelog_version"
const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy" const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
const val START_DIRECTORY = "start_directory" const val START_DIRECTORY = "start_directory"
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
const val LOCK_SCREEN = "lock_screen" const val LOCK_SCREEN = "lock_screen"
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order" const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position" const val LYRICS_OPTIONS = "lyrics_tab_position"

View file

@ -8,23 +8,24 @@ import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import io.github.muntashirakon.music.App
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity
import io.github.muntashirakon.music.adapter.song.ShuffleButtonSongAdapter import io.github.muntashirakon.music.adapter.song.ShuffleButtonSongAdapter
import io.github.muntashirakon.music.extensions.applyToolbar import io.github.muntashirakon.music.extensions.applyToolbar
import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.helper.menu.GenreMenuHelper import io.github.muntashirakon.music.helper.menu.GenreMenuHelper
import io.github.muntashirakon.music.interfaces.CabHolder import io.github.muntashirakon.music.interfaces.CabHolder
import io.github.muntashirakon.music.model.Genre import io.github.muntashirakon.music.model.Genre
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.mvp.presenter.GenreDetailsPresenter import io.github.muntashirakon.music.mvp.presenter.GenreDetailsPresenter
import io.github.muntashirakon.music.mvp.presenter.GenreDetailsPresenter.GenreDetailsPresenterImpl
import io.github.muntashirakon.music.mvp.presenter.GenreDetailsView import io.github.muntashirakon.music.mvp.presenter.GenreDetailsView
import io.github.muntashirakon.music.providers.RepositoryImpl
import io.github.muntashirakon.music.util.DensityUtil import io.github.muntashirakon.music.util.DensityUtil
import io.github.muntashirakon.music.util.RetroColorUtil import io.github.muntashirakon.music.util.RetroColorUtil
import com.afollestad.materialcab.MaterialCab import com.afollestad.materialcab.MaterialCab
import kotlinx.android.synthetic.main.activity_playlist_detail.* import kotlinx.android.synthetic.main.activity_playlist_detail.*
import java.util.* import java.util.*
import javax.inject.Inject
/** /**
* @author Hemanth S (h4h13). * @author Hemanth S (h4h13).
@ -32,9 +33,8 @@ import javax.inject.Inject
class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDetailsView { class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDetailsView {
@Inject
lateinit var genreDetailsPresenter: GenreDetailsPresenter
private lateinit var genreDetailsPresenter: GenreDetailsPresenter
private lateinit var genre: Genre private lateinit var genre: Genre
private lateinit var songAdapter: ShuffleButtonSongAdapter private lateinit var songAdapter: ShuffleButtonSongAdapter
private var cab: MaterialCab? = null private var cab: MaterialCab? = null
@ -62,16 +62,14 @@ class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDet
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
setBottomBarVisibility(View.GONE) setBottomBarVisibility(View.GONE)
if (intent.extras != null) {
genre = intent?.extras?.getParcelable(EXTRA_GENRE_ID)!! genre = extraNotNull<Genre>(EXTRA_GENRE_ID).value
} else {
finish()
}
setUpToolBar() setUpToolBar()
setupRecyclerView() setupRecyclerView()
App.musicComponent.inject(this) genreDetailsPresenter =
GenreDetailsPresenterImpl(RepositoryImpl(this))
genreDetailsPresenter.attachView(this) genreDetailsPresenter.attachView(this)
} }

View file

@ -1,72 +1,27 @@
package io.github.muntashirakon.music.activities package io.github.muntashirakon.music.activities
import android.R.attr
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.view.Menu
import android.view.* import android.view.MenuItem
import androidx.annotation.StringRes import android.view.WindowManager
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
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.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
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.appthemehelper.util.ToolbarContentTintHelper
import io.github.muntashirakon.music.App
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
import io.github.muntashirakon.music.extensions.surfaceColor import io.github.muntashirakon.music.extensions.surfaceColor
import io.github.muntashirakon.music.extensions.textColorSecondary
import io.github.muntashirakon.music.fragments.base.AbsMusicServiceFragment
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper
import io.github.muntashirakon.music.lyrics.LrcHelper import io.github.muntashirakon.music.lyrics.LrcView
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.model.lyrics.Lyrics
import io.github.muntashirakon.music.util.LyricUtil import io.github.muntashirakon.music.util.LyricUtil
import io.github.muntashirakon.music.util.MusicUtil import io.github.muntashirakon.music.util.RetroUtil
import io.github.muntashirakon.music.util.PreferenceUtil
import kotlinx.android.synthetic.main.activity_lyrics.* import kotlinx.android.synthetic.main.activity_lyrics.*
import kotlinx.android.synthetic.main.fragment_lyrics.*
import kotlinx.android.synthetic.main.fragment_synced.*
import java.io.File
class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback {
ViewPager.OnPageChangeListener { private lateinit var updateHelper: MusicProgressViewUpdateHelper
override fun onPageScrollStateChanged(state: Int) {
when (state) {
ViewPager.SCROLL_STATE_IDLE -> fab.show()
ViewPager.SCROLL_STATE_DRAGGING,
ViewPager.SCROLL_STATE_SETTLING -> fab.hide()
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
PreferenceUtil.lyricsOption = position
if (position == 0) fab.text = getString(R.string.synced_lyrics)
else if (position == 1) fab.text = getString(R.string.lyrics)
}
override fun onClick(v: View?) {
when (viewPager.currentItem) {
0 -> showSyncedLyrics()
1 -> showLyricsSaveDialog()
}
}
private lateinit var song: Song private lateinit var song: Song
private var lyricsString: String? = null
private val googleSearchLrcUrl: String private val googleSearchLrcUrl: String
get() { get() {
@ -84,56 +39,63 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener,
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setNavigationbarColorAuto() setNavigationbarColorAuto()
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() setupWakelock()
viewPager.apply {
adapter = PagerAdapter(supportFragmentManager)
currentItem = PreferenceUtil.lyricsOption
addOnPageChangeListener(this@LyricsActivity)
}
toolbar.setBackgroundColor(surfaceColor()) toolbar.setBackgroundColor(surfaceColor())
tabs.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar) ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
tabs.setupWithViewPager(viewPager)
tabs.setSelectedTabIndicator(
TintHelper.createTintedDrawable(
ContextCompat.getDrawable(
this,
R.drawable.tab_indicator
), ThemeStore.accentColor(this)
)
)
tabs.setTabTextColors(
textColorSecondary(),
ThemeStore.accentColor(this)
)
tabs.setSelectedTabIndicatorColor(ThemeStore.accentColor(this))
fab.setOnClickListener(this) updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
setupLyricsView()
}
private fun setupLyricsView() {
lyricsView.apply {
setCurrentColor(ThemeStore.accentColor(context))
setTimeTextColor(ThemeStore.accentColor(context))
setTimelineColor(ThemeStore.accentColor(context))
setTimelineTextColor(ThemeStore.accentColor(context))
setDraggable(true, LrcView.OnPlayClickListener {
MusicPlayerRemote.seekTo(it.toInt())
return@OnPlayClickListener true
})
}
}
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
lyricsView.updateTime(progress.toLong())
}
private fun loadLRCLyrics() {
lyricsView.setLabel("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricOriginalFile(song.data))
} else if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
lyricsView.loadLrc(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
} }
override fun onPlayingMetaChanged() { override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged() super.onPlayingMetaChanged()
updateTitleSong() updateTitleSong()
loadLRCLyrics()
} }
override fun onServiceConnected() { override fun onServiceConnected() {
super.onServiceConnected() super.onServiceConnected()
updateTitleSong() updateTitleSong()
loadLRCLyrics()
} }
private fun updateTitleSong() { private fun updateTitleSong() {
@ -146,269 +108,19 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener,
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_search, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) { if (item.itemId == android.R.id.home) {
finish() finish()
return true return true
} }
if (item.itemId == R.id.action_search) {
RetroUtil.openUrl(this, googleSearchLrcUrl)
}
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
}
private fun showSyncedLyrics() {
var content = ""
try {
content = LyricUtil.getStringFromFile(song.title, song.artistName)
} catch (e: Exception) {
e.printStackTrace()
}
/*val materialDialog = MaterialDialog(this)
.show {
title(R.string.add_time_framed_lryics)
negativeButton(R.string.action_search) {
RetroUtil.openUrl(this@LyricsActivity, googleSearchLrcUrl)
}
input(
hint = getString(R.string.paste_lyrics_here),
prefill = content,
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
) { _, input ->
LyricUtil.writeLrcToLoc(song.title, song.artistName, input.toString())
}
positiveButton(android.R.string.ok) {
updateSong()
}
}
MaterialUtil.setTint(materialDialog.getInputLayout(), false)*/
}
private fun updateSong() {
val page =
supportFragmentManager.findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + viewPager.currentItem)
if (viewPager.currentItem == 0 && page != null) {
(page as BaseLyricsFragment).upDateSong()
}
}
private fun showLyricsSaveDialog() {
val content: String = if (lyricsString == null) {
""
} else {
lyricsString!!
}
/*val materialDialog = MaterialDialog(
this
).show {
title(R.string.add_lyrics)
negativeButton(R.string.action_search) {
RetroUtil.openUrl(this@LyricsActivity, getGoogleSearchUrl())
}
input(
hint = getString(R.string.paste_lyrics_here),
prefill = content,
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
) { _, input ->
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
WriteTagsAsyncTask(this@LyricsActivity).execute(
WriteTagsAsyncTask.LoadingInfo(
getSongPaths(song), fieldKeyValueMap, null
)
)
}
positiveButton(android.R.string.ok) {
updateSong()
}
}
MaterialUtil.setTint(materialDialog.getInputLayout(), false)*/
}
private fun getSongPaths(song: Song): ArrayList<String> {
val paths = ArrayList<String>(1)
paths.add(song.data)
return paths
}
private fun getGoogleSearchUrl(): String {
var baseUrl = "http://www.google.com/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+") + " lyrics"
baseUrl += query
return baseUrl
}
class PagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(
fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {
class Tabs(
@StringRes val title: Int, val fragment: Fragment
)
private var tabs = ArrayList<Tabs>()
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
tabs.add(Tabs(R.string.synced_lyrics, SyncedLyricsFragment()))
}
tabs.add(Tabs(R.string.normal_lyrics, OfflineLyricsFragment()))
}
override fun getItem(position: Int): Fragment {
return tabs[position].fragment
}
override fun getPageTitle(position: Int): CharSequence? {
return App.getContext().getString(tabs[position].title)
}
override fun getCount(): Int {
return tabs.size
}
}
abstract class BaseLyricsFragment : AbsMusicServiceFragment() {
abstract fun upDateSong()
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
upDateSong()
}
override fun onServiceConnected() {
super.onServiceConnected()
upDateSong()
}
}
class OfflineLyricsFragment : BaseLyricsFragment() {
override fun upDateSong() {
loadSongLyrics()
}
private var updateLyricsAsyncTask: AsyncTask<*, *, *>? = null
private var lyrics: Lyrics? = null
@SuppressLint("StaticFieldLeak")
private fun loadSongLyrics() {
if (updateLyricsAsyncTask != null) {
updateLyricsAsyncTask!!.cancel(false)
}
val song = MusicPlayerRemote.currentSong
updateLyricsAsyncTask = object : AsyncTask<Void?, Void?, Lyrics?>() {
override fun doInBackground(vararg params: Void?): Lyrics? {
val data = MusicUtil.getLyrics(song)
return if (TextUtils.isEmpty(data)) {
null
} else Lyrics.parse(song, data!!)
}
override fun onPreExecute() {
super.onPreExecute()
lyrics = null
}
override fun onPostExecute(l: Lyrics?) {
lyrics = l
offlineLyrics?.visibility = View.VISIBLE
if (l == null) {
offlineLyrics?.setText(R.string.no_lyrics_found)
return
}
(activity as LyricsActivity).lyricsString = l.text
offlineLyrics?.text = l.text
}
override fun onCancelled(s: Lyrics?) {
onPostExecute(null)
}
}.execute()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
loadSongLyrics()
}
override fun onDestroyView() {
super.onDestroyView()
if (updateLyricsAsyncTask != null && !updateLyricsAsyncTask!!.isCancelled) {
updateLyricsAsyncTask?.cancel(true)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_lyrics, container, false)
}
}
class SyncedLyricsFragment : BaseLyricsFragment(), MusicProgressViewUpdateHelper.Callback {
override fun upDateSong() {
loadLRCLyrics()
}
private lateinit var updateHelper: MusicProgressViewUpdateHelper
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_synced, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupLyricsView()
}
private fun setupLyricsView() {
lyricsView.apply {
setCurrentPlayLineColor(ThemeStore.accentColor(requireContext()))
setIndicatorTextColor(ThemeStore.accentColor(requireContext()))
setCurrentIndicateLineTextColor(
resolveColor(
requireContext(),
attr.textColorPrimary
)
)
setNoLrcTextColor(resolveColor(requireContext(), attr.textColorPrimary))
setOnPlayIndicatorLineListener { time, _ -> MusicPlayerRemote.seekTo(time.toInt()) }
}
}
override fun onResume() {
super.onResume()
updateHelper.start()
}
override fun onPause() {
super.onPause()
updateHelper.stop()
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
lyricsView.updateTime(progress.toLong())
}
private fun loadLRCLyrics() {
lyricsView.resetView("Empty")
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
showLyricsLocal(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
private fun showLyricsLocal(file: File?) {
if (file != null) {
lyricsView.setLrcData(LrcHelper.parseLrcFromFile(file))
}
}
}
}

View file

@ -7,13 +7,13 @@ import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import io.github.muntashirakon.music.App
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity
import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter
import io.github.muntashirakon.music.adapter.song.PlaylistSongAdapter import io.github.muntashirakon.music.adapter.song.PlaylistSongAdapter
import io.github.muntashirakon.music.adapter.song.SongAdapter import io.github.muntashirakon.music.adapter.song.SongAdapter
import io.github.muntashirakon.music.extensions.applyToolbar import io.github.muntashirakon.music.extensions.applyToolbar
import io.github.muntashirakon.music.extensions.extraNotNull
import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper import io.github.muntashirakon.music.helper.menu.PlaylistMenuHelper
import io.github.muntashirakon.music.interfaces.CabHolder import io.github.muntashirakon.music.interfaces.CabHolder
import io.github.muntashirakon.music.loaders.PlaylistLoader import io.github.muntashirakon.music.loaders.PlaylistLoader
@ -21,7 +21,9 @@ import io.github.muntashirakon.music.model.AbsCustomPlaylist
import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.Playlist
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.mvp.presenter.PlaylistSongsPresenter import io.github.muntashirakon.music.mvp.presenter.PlaylistSongsPresenter
import io.github.muntashirakon.music.mvp.presenter.PlaylistSongsPresenter.PlaylistSongsPresenterImpl
import io.github.muntashirakon.music.mvp.presenter.PlaylistSongsView import io.github.muntashirakon.music.mvp.presenter.PlaylistSongsView
import io.github.muntashirakon.music.providers.RepositoryImpl
import io.github.muntashirakon.music.util.DensityUtil import io.github.muntashirakon.music.util.DensityUtil
import io.github.muntashirakon.music.util.PlaylistsUtil import io.github.muntashirakon.music.util.PlaylistsUtil
import io.github.muntashirakon.music.util.RetroColorUtil import io.github.muntashirakon.music.util.RetroColorUtil
@ -30,13 +32,11 @@ import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemA
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playlist_detail.* import kotlinx.android.synthetic.main.activity_playlist_detail.*
import javax.inject.Inject
class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsView { class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsView {
@Inject
lateinit var playlistSongsPresenter: PlaylistSongsPresenter
private lateinit var presenter: PlaylistSongsPresenter
private lateinit var playlist: Playlist private lateinit var playlist: Playlist
private var cab: MaterialCab? = null private var cab: MaterialCab? = null
private lateinit var adapter: SongAdapter private lateinit var adapter: SongAdapter
@ -52,14 +52,10 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
setLightNavigationBar(true) setLightNavigationBar(true)
setBottomBarVisibility(View.GONE) setBottomBarVisibility(View.GONE)
App.musicComponent.inject(this) presenter = PlaylistSongsPresenterImpl(RepositoryImpl(this))
playlistSongsPresenter.attachView(this) presenter.attachView(this)
if (intent.extras != null) { playlist = extraNotNull<Playlist>(EXTRA_PLAYLIST).value
playlist = intent.extras!!.getParcelable(EXTRA_PLAYLIST)!!
} else {
finish()
}
setUpToolBar() setUpToolBar()
setUpRecyclerView() setUpRecyclerView()
@ -114,7 +110,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
playlistSongsPresenter.loadPlaylistSongs(playlist) presenter.loadPlaylistSongs(playlist)
} }
private fun setUpToolBar() { private fun setUpToolBar() {
@ -181,7 +177,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
setToolbarTitle(playlist.name) setToolbarTitle(playlist.name)
} }
} }
playlistSongsPresenter.loadPlaylistSongs(playlist) presenter.loadPlaylistSongs(playlist)
} }
private fun setToolbarTitle(title: String) { private fun setToolbarTitle(title: String) {
@ -227,7 +223,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
wrappedAdapter = null wrappedAdapter = null
} }
super.onDestroy() super.onDestroy()
playlistSongsPresenter.detachView() presenter.detachView()
} }
override fun showEmptyView() { override fun showEmptyView() {

View file

@ -21,23 +21,22 @@ import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import io.github.muntashirakon.music.App
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
import io.github.muntashirakon.music.adapter.SearchAdapter import io.github.muntashirakon.music.adapter.SearchAdapter
import io.github.muntashirakon.music.mvp.presenter.SearchPresenter import io.github.muntashirakon.music.mvp.presenter.SearchPresenter
import io.github.muntashirakon.music.mvp.presenter.SearchPresenter.SearchPresenterImpl
import io.github.muntashirakon.music.mvp.presenter.SearchView import io.github.muntashirakon.music.mvp.presenter.SearchView
import io.github.muntashirakon.music.providers.RepositoryImpl
import io.github.muntashirakon.music.util.RetroUtil import io.github.muntashirakon.music.util.RetroUtil
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import kotlinx.android.synthetic.main.activity_search.* import kotlinx.android.synthetic.main.activity_search.*
import java.util.* import java.util.*
import javax.inject.Inject
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatcher, SearchView { class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatcher, SearchView {
@Inject
lateinit var searchPresenter: SearchPresenter
private lateinit var presenter: SearchPresenter
private var searchAdapter: SearchAdapter? = null private var searchAdapter: SearchAdapter? = null
private var query: String? = null private var query: String? = null
@ -50,8 +49,8 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
setLightNavigationBar(true) setLightNavigationBar(true)
App.musicComponent.inject(this) presenter = SearchPresenterImpl(RepositoryImpl(this))
searchPresenter.attachView(this) presenter.attachView(this)
setupRecyclerView() setupRecyclerView()
setUpToolBar() setUpToolBar()
@ -117,7 +116,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
searchPresenter.detachView() presenter.detachView()
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@ -134,7 +133,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
TransitionManager.beginDelayedTransition(appBarLayout) TransitionManager.beginDelayedTransition(appBarLayout)
voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE
clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE
searchPresenter.search(query) presenter.search(query)
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
@ -176,7 +175,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
query = result?.get(0) query = result?.get(0)
searchView.setText(query, BufferType.EDITABLE) searchView.setText(query, BufferType.EDITABLE)
searchPresenter.search(query!!) presenter.search(query!!)
} }
} }
} }

View file

@ -63,7 +63,7 @@ class UserInfoActivity : AbsBaseActivity() {
next.setOnClickListener { next.setOnClickListener {
val nameString = name.text.toString().trim { it <= ' ' } val nameString = name.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(nameString)) { if (TextUtils.isEmpty(nameString)) {
Toast.makeText(this, "Umm name is empty", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Umm you're name can't be empty!", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
} }
PreferenceUtil.userName = nameString PreferenceUtil.userName = nameString

View file

@ -4,24 +4,26 @@ import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.IntDef import androidx.annotation.IntDef
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.album.AlbumFullWidthAdapter import io.github.muntashirakon.music.adapter.album.AlbumFullWidthAdapter
import io.github.muntashirakon.music.adapter.artist.ArtistAdapter import io.github.muntashirakon.music.adapter.artist.ArtistAdapter
import io.github.muntashirakon.music.adapter.song.SongAdapter import io.github.muntashirakon.music.adapter.song.SongAdapter
import io.github.muntashirakon.music.extensions.show import io.github.muntashirakon.music.extensions.show
import io.github.muntashirakon.music.glide.SongGlideRequest
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.loaders.PlaylistSongsLoader import io.github.muntashirakon.music.loaders.PlaylistSongsLoader
import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.*
import io.github.muntashirakon.music.model.Artist
import io.github.muntashirakon.music.model.Home
import io.github.muntashirakon.music.model.Playlist import io.github.muntashirakon.music.model.Playlist
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import com.bumptech.glide.Glide
class HomeAdapter( class HomeAdapter(
private val activity: AppCompatActivity, private val activity: AppCompatActivity,
@ -40,6 +42,15 @@ class HomeAdapter(
return when (viewType) { return when (viewType) {
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout) RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
PLAYLISTS -> PlaylistViewHolder(layout) PLAYLISTS -> PlaylistViewHolder(layout)
SUGGESTIONS -> {
SuggestionsViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.item_suggestions,
parent,
false
)
)
}
else -> { else -> {
AlbumViewHolder( AlbumViewHolder(
LayoutInflater.from(activity).inflate( LayoutInflater.from(activity).inflate(
@ -56,23 +67,41 @@ class HomeAdapter(
when (getItemViewType(position)) { when (getItemViewType(position)) {
RECENT_ALBUMS -> { RECENT_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.recent_albums) viewHolder.bindView(
list[position].arrayList as List<Album>,
R.string.recent_albums
)
} }
TOP_ALBUMS -> { TOP_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.top_albums) viewHolder.bindView(
list[position].arrayList as List<Album>,
R.string.top_albums
)
} }
RECENT_ARTISTS -> { RECENT_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(list[position].arrayList.toArtists(), R.string.recent_artists) viewHolder.bindView(
list[position].arrayList as List<Artist>,
R.string.recent_artists
)
} }
TOP_ARTISTS -> { TOP_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(list[position].arrayList.toArtists(), R.string.top_artists) viewHolder.bindView(list[position].arrayList as List<Artist>, R.string.top_artists)
} }
PLAYLISTS -> { PLAYLISTS -> {
val viewHolder = holder as PlaylistViewHolder val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(list[position].arrayList.toPlaylist(), R.string.favorites) viewHolder.bindView(
list[position].arrayList as List<Playlist>,
R.string.favorites
)
}
SUGGESTIONS -> {
val viewHolder = holder as SuggestionsViewHolder
viewHolder.bindView(
list[position].arrayList as List<Song>
)
} }
} }
} }
@ -88,7 +117,7 @@ class HomeAdapter(
companion object { companion object {
@IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS) @IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS, SUGGESTIONS)
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
annotation class HomeSection annotation class HomeSection
@ -96,11 +125,12 @@ class HomeAdapter(
const val TOP_ALBUMS = 1 const val TOP_ALBUMS = 1
const val RECENT_ARTISTS = 2 const val RECENT_ARTISTS = 2
const val TOP_ARTISTS = 0 const val TOP_ARTISTS = 0
const val PLAYLISTS = 4 const val SUGGESTIONS = 4
const val PLAYLISTS = 5
} }
private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) { private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(list: ArrayList<Album>, titleRes: Int) { fun bindView(list: List<Album>, titleRes: Int) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
recyclerView.apply { recyclerView.apply {
show() show()
@ -112,7 +142,7 @@ class HomeAdapter(
} }
inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) { inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(list: ArrayList<Artist>, titleRes: Int) { fun bindView(list: List<Artist>, titleRes: Int) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
val manager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) val manager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
val artistAdapter = ArtistAdapter( val artistAdapter = ArtistAdapter(
@ -131,8 +161,37 @@ class HomeAdapter(
} }
} }
private inner class SuggestionsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val images = listOf(
R.id.image1,
R.id.image2,
R.id.image3,
R.id.image4,
R.id.image5,
R.id.image6,
R.id.image7,
R.id.image8
)
fun bindView(arrayList: List<Song>) {
val color = ThemeStore.accentColor(activity)
itemView.findViewById<TextView>(R.id.text).setTextColor(color)
images.forEachIndexed { index, i ->
itemView.findViewById<View>(i).setOnClickListener {
MusicPlayerRemote.playNext(arrayList[index])
}
SongGlideRequest.Builder.from(Glide.with(activity), arrayList[index])
.asBitmap()
.build()
.into(itemView.findViewById(i))
}
}
}
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) { private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(arrayList: ArrayList<Playlist>, titleRes: Int) { fun bindView(arrayList: List<Playlist>, titleRes: Int) {
if (arrayList.isNotEmpty()) { if (arrayList.isNotEmpty()) {
val songs = PlaylistSongsLoader.getPlaylistSongList(activity, arrayList[0]) val songs = PlaylistSongsLoader.getPlaylistSongList(activity, arrayList[0])
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
@ -155,28 +214,3 @@ class HomeAdapter(
val title: AppCompatTextView = itemView.findViewById(R.id.title) val title: AppCompatTextView = itemView.findViewById(R.id.title)
} }
} }
private fun <E> ArrayList<E>.toAlbums(): ArrayList<Album> {
val arrayList = ArrayList<Album>()
for (x in this) {
arrayList.add(x as Album)
}
return arrayList
}
private fun <E> ArrayList<E>.toArtists(): ArrayList<Artist> {
val arrayList = ArrayList<Artist>()
for (x in this) {
arrayList.add(x as Artist)
}
return arrayList
}
private fun <E> ArrayList<E>.toPlaylist(): ArrayList<Playlist> {
val arrayList = ArrayList<Playlist>()
for (x in this) {
arrayList.add(x as Playlist)
}
return arrayList
}

View file

@ -14,11 +14,16 @@ import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
import io.github.muntashirakon.music.glide.SongGlideRequest import io.github.muntashirakon.music.glide.SongGlideRequest
import io.github.muntashirakon.music.misc.CustomFragmentStatePagerAdapter import io.github.muntashirakon.music.misc.CustomFragmentStatePagerAdapter
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.NavigationUtil import io.github.muntashirakon.music.util.NavigationUtil
import io.github.muntashirakon.music.util.PreferenceUtil import io.github.muntashirakon.music.util.PreferenceUtil
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AlbumCoverPagerAdapter( class AlbumCoverPagerAdapter(
fragmentManager: FragmentManager, fragmentManager: FragmentManager,
@ -85,20 +90,33 @@ class AlbumCoverPagerAdapter(
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
albumCover = view.findViewById(R.id.player_image) albumCover = view.findViewById(R.id.player_image)
albumCover.setOnClickListener { albumCover.setOnClickListener {
NavigationUtil.goToLyrics(requireActivity()) showLyricsDialog()
} }
return view return view
} }
private fun showLyricsDialog() {
GlobalScope.launch(Dispatchers.IO) {
val data = MusicUtil.getLyrics(song)
withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
).apply {
setTitle(song.title)
setMessage(data)
setNegativeButton(R.string.synced_lyrics) { _, _ ->
NavigationUtil.goToLyrics(requireActivity())
}
show()
}
}
}
}
private fun getLayoutWithPlayerTheme(): Int { private fun getLayoutWithPlayerTheme(): Int {
return when (PreferenceUtil.nowPlayingScreen) { return when (PreferenceUtil.nowPlayingScreen) {
Card, Card, Fit, Tiny, Classic, Peak, Gradient, Full -> R.layout.fragment_album_full_cover
Fit,
Tiny,
Classic,
Peak,
Gradient,
Full -> R.layout.fragment_album_full_cover
else -> { else -> {
if (PreferenceUtil.isCarouselEffect) { if (PreferenceUtil.isCarouselEffect) {
R.layout.fragment_album_carousel_cover R.layout.fragment_album_carousel_cover

View file

@ -1,63 +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 io.github.muntashirakon.music.dagger
import io.github.muntashirakon.music.activities.artists.ArtistDetailActivity
import io.github.muntashirakon.music.activities.GenreDetailsActivity
import io.github.muntashirakon.music.activities.PlaylistDetailActivity
import io.github.muntashirakon.music.activities.SearchActivity
import io.github.muntashirakon.music.dagger.module.AppModule
import io.github.muntashirakon.music.dagger.module.PresenterModule
import io.github.muntashirakon.music.fragments.albums.AlbumsFragment
import io.github.muntashirakon.music.fragments.artists.ArtistsFragment
import io.github.muntashirakon.music.fragments.genres.GenresFragment
import io.github.muntashirakon.music.fragments.home.BannerHomeFragment
import io.github.muntashirakon.music.fragments.playlists.PlaylistsFragment
import io.github.muntashirakon.music.fragments.songs.SongsFragment
import dagger.Component
import javax.inject.Singleton
/**
* Created by hemanths on 2019-09-04.
*/
@Singleton
@Component(
modules = [
AppModule::class,
PresenterModule::class
]
)
interface MusicComponent {
fun inject(songsFragment: SongsFragment)
fun inject(albumsFragment: AlbumsFragment)
fun inject(artistsFragment: ArtistsFragment)
fun inject(genresFragment: GenresFragment)
fun inject(playlistsFragment: PlaylistsFragment)
fun inject(artistDetailActivity: ArtistDetailActivity)
fun inject(playlistDetailActivity: PlaylistDetailActivity)
fun inject(genreDetailsActivity: GenreDetailsActivity)
fun inject(searchActivity: SearchActivity)
fun inject(bannerHomeFragment: BannerHomeFragment)
}

View file

@ -1,33 +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 io.github.muntashirakon.music.dagger.module
import android.app.Application
import android.content.Context
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by hemanths on 2019-09-04.
*/
@Module
class AppModule(private val application: Application) {
@Provides
@Singleton
fun provideContext(): Context = application
}

View file

@ -1,55 +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 io.github.muntashirakon.music.dagger.module
import android.content.Context
import io.github.muntashirakon.music.mvp.presenter.GenreDetailsPresenter
import io.github.muntashirakon.music.mvp.presenter.GenreDetailsPresenter.GenreDetailsPresenterImpl
import io.github.muntashirakon.music.mvp.presenter.PlaylistSongsPresenter
import io.github.muntashirakon.music.mvp.presenter.PlaylistSongsPresenter.PlaylistSongsPresenterImpl
import io.github.muntashirakon.music.mvp.presenter.SearchPresenter
import io.github.muntashirakon.music.mvp.presenter.SearchPresenter.SearchPresenterImpl
import io.github.muntashirakon.music.providers.RepositoryImpl
import io.github.muntashirakon.music.providers.interfaces.Repository
import dagger.Module
import dagger.Provides
/**
* Created by hemanths on 2019-12-30.
*/
@Module
class PresenterModule {
@Provides
fun providesRepository(context: Context): Repository {
return RepositoryImpl(context)
}
@Provides
fun providesGenreDetailsPresenter(presenter: GenreDetailsPresenterImpl): GenreDetailsPresenter {
return presenter
}
@Provides
fun providesPlaylistSongPresenter(presenter: PlaylistSongsPresenterImpl): PlaylistSongsPresenter {
return presenter
}
@Provides
fun providesSearchPresenter(presenter: SearchPresenterImpl): SearchPresenter {
return presenter
}
}

View file

@ -55,7 +55,8 @@ class LibraryViewModel(application: Application) :
_repository.topAlbums(), _repository.topAlbums(),
_repository.recentArtists(), _repository.recentArtists(),
_repository.recentAlbums(), _repository.recentAlbums(),
_repository.favoritePlaylist() _repository.favoritePlaylist(),
_repository.suggestions()
) )
for (r in result) { for (r in result) {
if (r != null) { if (r != null) {

View file

@ -18,7 +18,6 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.github.muntashirakon.music.App
import io.github.muntashirakon.music.R import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.adapter.GenreAdapter import io.github.muntashirakon.music.adapter.GenreAdapter
import io.github.muntashirakon.music.fragments.base.AbsLibraryPagerRecyclerViewFragment import io.github.muntashirakon.music.fragments.base.AbsLibraryPagerRecyclerViewFragment
@ -27,11 +26,6 @@ import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks
class GenresFragment : AbsLibraryPagerRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(), class GenresFragment : AbsLibraryPagerRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
MainActivityFragmentCallbacks { MainActivityFragmentCallbacks {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
App.musicComponent.inject(this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
mainActivity.libraryViewModel.allGenres().observe( mainActivity.libraryViewModel.allGenres().observe(

View file

@ -55,13 +55,6 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba
) )
} }
private fun loadImageFromStorage() {
UserProfileGlideRequest.Builder.from(
Glide.with(requireActivity()),
UserProfileGlideRequest.getUserModel()
).build().into(userImage)
}
private val displayMetrics: DisplayMetrics private val displayMetrics: DisplayMetrics
get() { get() {
val display = mainActivity.windowManager.defaultDisplay val display = mainActivity.windowManager.defaultDisplay
@ -111,8 +104,7 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba
) )
NavigationUtil.goToUserInfo(requireActivity(), options) NavigationUtil.goToUserInfo(requireActivity(), options)
} }
titleWelcome?.text = titleWelcome?.text = String.format("%s", PreferenceUtil.userName)
String.format("%s", PreferenceUtil.userName)
homeAdapter = HomeAdapter(mainActivity, displayMetrics) homeAdapter = HomeAdapter(mainActivity, displayMetrics)
recyclerView.apply { recyclerView.apply {

View file

@ -33,7 +33,6 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(), ViewPager.OnPageChan
fun removeSlideEffect() { fun removeSlideEffect() {
val transformer = ParallaxPagerTransformer(R.id.player_image) val transformer = ParallaxPagerTransformer(R.id.player_image)
transformer.setSpeed(0.3f) transformer.setSpeed(0.3f)
//viewPager.setPageTransformer(true, transformer)
} }
override fun onCreateView( override fun onCreateView(

View file

@ -4,8 +4,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
@ -16,104 +14,11 @@ import io.github.muntashirakon.music.extensions.textColorSecondary
import io.github.muntashirakon.music.fragments.base.AbsPlayerFragment import io.github.muntashirakon.music.fragments.base.AbsPlayerFragment
import io.github.muntashirakon.music.fragments.player.PlayerAlbumCoverFragment import io.github.muntashirakon.music.fragments.player.PlayerAlbumCoverFragment
import io.github.muntashirakon.music.helper.MusicPlayerRemote import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.helper.MusicProgressViewUpdateHelper
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.model.lyrics.AbsSynchronizedLyrics
import io.github.muntashirakon.music.model.lyrics.Lyrics
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
import kotlinx.android.synthetic.main.fragment_adaptive_player.* import kotlinx.android.synthetic.main.fragment_adaptive_player.*
class AdaptiveFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Callback { class AdaptiveFragment : AbsPlayerFragment() {
private lateinit var lyricsLayout: FrameLayout
private lateinit var lyricsLine1: TextView
private lateinit var lyricsLine2: TextView
private var lyrics: Lyrics? = null
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
override fun onUpdateProgressViews(progress: Int, total: Int) {
if (!isLyricsLayoutBound()) return
if (!isLyricsLayoutVisible()) {
hideLyricsLayout()
return
}
if (lyrics !is AbsSynchronizedLyrics) return
val synchronizedLyrics = lyrics as AbsSynchronizedLyrics
lyricsLayout.visibility = View.VISIBLE
lyricsLayout.alpha = 1f
val oldLine = lyricsLine2.text.toString()
val line = synchronizedLyrics.getLine(progress)
if (oldLine != line || oldLine.isEmpty()) {
lyricsLine1.text = oldLine
lyricsLine2.text = line
lyricsLine1.visibility = View.VISIBLE
lyricsLine2.visibility = View.VISIBLE
lyricsLine2.measure(
View.MeasureSpec.makeMeasureSpec(
lyricsLine2.measuredWidth,
View.MeasureSpec.EXACTLY
),
View.MeasureSpec.UNSPECIFIED
)
val h: Float = lyricsLine2.measuredHeight.toFloat()
lyricsLine1.alpha = 1f
lyricsLine1.translationY = 0f
lyricsLine1.animate().alpha(0f).translationY(-h).duration = VISIBILITY_ANIM_DURATION
lyricsLine2.alpha = 0f
lyricsLine2.translationY = h
lyricsLine2.animate().alpha(1f).translationY(0f).duration = VISIBILITY_ANIM_DURATION
}
}
private fun isLyricsLayoutVisible(): Boolean {
return lyrics != null && lyrics!!.isSynchronized && lyrics!!.isValid
}
private fun isLyricsLayoutBound(): Boolean {
return lyricsLayout != null && lyricsLine1 != null && lyricsLine2 != null
}
private fun hideLyricsLayout() {
lyricsLayout.animate().alpha(0f).setDuration(VISIBILITY_ANIM_DURATION)
.withEndAction(Runnable {
if (!isLyricsLayoutBound()) return@Runnable
lyricsLayout.visibility = View.GONE
lyricsLine1.text = null
lyricsLine2.text = null
})
}
override fun setLyrics(l: Lyrics?) {
lyrics = l
if (!isLyricsLayoutBound()) return
if (!isLyricsLayoutVisible()) {
hideLyricsLayout()
return
}
lyricsLine1.text = null
lyricsLine2.text = null
lyricsLayout.visibility = View.VISIBLE
lyricsLayout.animate().alpha(1f).duration = VISIBILITY_ANIM_DURATION
}
override fun onDestroyView() {
super.onDestroyView()
progressViewUpdateHelper.stop()
}
override fun playerToolbar(): Toolbar { override fun playerToolbar(): Toolbar {
return playerToolbar return playerToolbar
@ -132,15 +37,8 @@ class AdaptiveFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Call
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
lyricsLayout = view.findViewById(R.id.player_lyrics)
lyricsLine1 = view.findViewById(R.id.player_lyrics_line1)
lyricsLine2 = view.findViewById(R.id.player_lyrics_line2)
setUpSubFragments() setUpSubFragments()
setUpPlayerToolbar() setUpPlayerToolbar()
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
progressViewUpdateHelper.start()
} }
private fun setUpSubFragments() { private fun setUpSubFragments() {

View file

@ -279,6 +279,10 @@ class GradientPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelpe
updateQueuePosition() updateQueuePosition()
} }
override fun onQueueChanged() {
super.onQueueChanged()
updateLabel()
}
private fun updateSong() { private fun updateSong() {
val song = MusicPlayerRemote.currentSong val song = MusicPlayerRemote.currentSong
title.text = song.title title.text = song.title

View file

@ -17,14 +17,18 @@ package io.github.muntashirakon.music.loaders
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.provider.BaseColumns import android.provider.BaseColumns
import android.provider.MediaStore
import io.github.muntashirakon.music.Constants.NUMBER_OF_TOP_TRACKS import io.github.muntashirakon.music.Constants.NUMBER_OF_TOP_TRACKS
import io.github.muntashirakon.music.loaders.SongLoader.makeSongCursor
import io.github.muntashirakon.music.model.Album import io.github.muntashirakon.music.model.Album
import io.github.muntashirakon.music.model.Artist import io.github.muntashirakon.music.model.Artist
import io.github.muntashirakon.music.model.Song import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.providers.HistoryStore import io.github.muntashirakon.music.providers.HistoryStore
import io.github.muntashirakon.music.providers.SongPlayCountStore import io.github.muntashirakon.music.providers.SongPlayCountStore
import io.github.muntashirakon.music.util.PreferenceUtil
import java.util.* import java.util.*
/** /**
* Created by hemanths on 16/08/17. * Created by hemanths on 16/08/17.
*/ */
@ -39,19 +43,23 @@ object TopAndRecentlyPlayedTracksLoader {
return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context)) return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context))
} }
private fun makeRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? { fun getNotRecentlyPlayedTracks(context: Context): ArrayList<Song> {
val retCursor = makeRecentTracksCursorImpl(context) val allSongs = SongLoader.getSongs(
makeSongCursor(
// clean up the databases with any ids not found context,
if (retCursor != null) { null, null,
val missingIds = retCursor.missingIds MediaStore.Audio.Media.DATE_ADDED + " ASC"
if (missingIds != null && missingIds.size > 0) { )
for (id in missingIds) { )
HistoryStore.getInstance(context).removeSongId(id) val playedSongs = SongLoader.getSongs(
} makePlayedTracksCursorAndClearUpDatabase(context)
} )
} val notRecentlyPlayedSongs = SongLoader.getSongs(
return retCursor makeNotRecentTracksCursorAndClearUpDatabase(context)
)
allSongs.removeAll(playedSongs)
allSongs.addAll(notRecentlyPlayedSongs)
return allSongs
} }
private fun makeTopTracksCursorAndClearUpDatabase(context: Context): Cursor? { private fun makeTopTracksCursorAndClearUpDatabase(context: Context): Cursor? {
@ -72,21 +80,19 @@ object TopAndRecentlyPlayedTracksLoader {
private fun makeRecentTracksCursorImpl(context: Context): SortedLongCursor? { private fun makeRecentTracksCursorImpl(context: Context): SortedLongCursor? {
// first get the top results ids from the internal database // first get the top results ids from the internal database
val songs = HistoryStore.getInstance(context).queryRecentIds() val songs = HistoryStore.getInstance(context).queryRecentIds()
songs.use {
try {
return makeSortedCursor( return makeSortedCursor(
context, songs, context,
songs!!.getColumnIndex(HistoryStore.RecentStoreColumns.ID) it,
it.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
) )
} finally {
songs?.close()
} }
} }
private fun makeTopTracksCursorImpl(context: Context): SortedLongCursor? { private fun makeTopTracksCursorImpl(context: Context): SortedLongCursor? {
// first get the top results ids from the internal database // first get the top results ids from the internal database
val songs = SongPlayCountStore.getInstance(context) val songs =
.getTopPlayedResults(NUMBER_OF_TOP_TRACKS) SongPlayCountStore.getInstance(context).getTopPlayedResults(NUMBER_OF_TOP_TRACKS)
songs.use { localSongs -> songs.use { localSongs ->
return makeSortedCursor( return makeSortedCursor(
@ -145,4 +151,57 @@ object TopAndRecentlyPlayedTracksLoader {
fun getTopArtists(context: Context): ArrayList<Artist> { fun getTopArtists(context: Context): ArrayList<Artist> {
return ArtistLoader.splitIntoArtists(getTopAlbums(context)) return ArtistLoader.splitIntoArtists(getTopAlbums(context))
} }
fun makeRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, false, false)
}
fun makePlayedTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, true, false)
}
fun makeNotRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, false, true)
}
private fun makeRecentTracksCursorAndClearUpDatabaseImpl(
context: Context,
ignoreCutoffTime: Boolean,
reverseOrder: Boolean
): SortedLongCursor? {
val retCursor = makeRecentTracksCursorImpl(context, ignoreCutoffTime, reverseOrder)
// clean up the databases with any ids not found
// clean up the databases with any ids not found
if (retCursor != null) {
val missingIds = retCursor.missingIds
if (missingIds != null && missingIds.size > 0) {
for (id in missingIds) {
HistoryStore.getInstance(context).removeSongId(id)
}
}
}
return retCursor
}
private fun makeRecentTracksCursorImpl(
context: Context,
ignoreCutoffTime: Boolean,
reverseOrder: Boolean
): SortedLongCursor? {
val cutoff =
(if (ignoreCutoffTime) 0 else PreferenceUtil.getRecentlyPlayedCutoffTimeMillis()).toLong()
val songs =
HistoryStore.getInstance(context).queryRecentIds(cutoff * if (reverseOrder) -1 else 1)
return songs.use {
makeSortedCursor(
context,
it,
it.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
)
}
}
} }

View file

@ -0,0 +1,117 @@
/*
* Copyright (C) 2017 wangchenyan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package code.name.monkey.retromusic.lyrics;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
/**
* 一行歌词实体
*/
class LrcEntry implements Comparable<LrcEntry> {
private long time;
private String text;
private String secondText;
private StaticLayout staticLayout;
/**
* 歌词距离视图顶部的距离
*/
private float offset = Float.MIN_VALUE;
public static final int GRAVITY_CENTER = 0;
public static final int GRAVITY_LEFT = 1;
public static final int GRAVITY_RIGHT = 2;
LrcEntry(long time, String text) {
this.time = time;
this.text = text;
}
LrcEntry(long time, String text, String secondText) {
this.time = time;
this.text = text;
this.secondText = secondText;
}
void init(TextPaint paint, int width, int gravity) {
Layout.Alignment align;
switch (gravity) {
case GRAVITY_LEFT:
align = Layout.Alignment.ALIGN_NORMAL;
break;
default:
case GRAVITY_CENTER:
align = Layout.Alignment.ALIGN_CENTER;
break;
case GRAVITY_RIGHT:
align = Layout.Alignment.ALIGN_OPPOSITE;
break;
}
staticLayout = new StaticLayout(getShowText(), paint, width, align, 1f, 0f, false);
offset = Float.MIN_VALUE;
}
long getTime() {
return time;
}
StaticLayout getStaticLayout() {
return staticLayout;
}
int getHeight() {
if (staticLayout == null) {
return 0;
}
return staticLayout.getHeight();
}
public float getOffset() {
return offset;
}
public void setOffset(float offset) {
this.offset = offset;
}
String getText() {
return text;
}
void setSecondText(String secondText) {
this.secondText = secondText;
}
private String getShowText() {
if (!TextUtils.isEmpty(secondText)) {
return text + "\n" + secondText;
} else {
return text;
}
}
@Override
public int compareTo(LrcEntry entry) {
if (entry == null) {
return -1;
}
return (int) (time - entry.getTime());
}
}

View file

@ -0,0 +1,232 @@
/*
* Copyright (C) 2017 wangchenyan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package code.name.monkey.retromusic.lyrics;
import android.animation.ValueAnimator;
import android.text.TextUtils;
import android.text.format.DateUtils;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 工具类
*/
class LrcUtils {
private static final Pattern PATTERN_LINE = Pattern.compile("((\\[\\d\\d:\\d\\d\\.\\d{2,3}\\])+)(.+)");
private static final Pattern PATTERN_TIME = Pattern.compile("\\[(\\d\\d):(\\d\\d)\\.(\\d{2,3})\\]");
/**
* 从文件解析双语歌词
*/
static List<LrcEntry> parseLrc(File[] lrcFiles) {
if (lrcFiles == null || lrcFiles.length != 2 || lrcFiles[0] == null) {
return null;
}
File mainLrcFile = lrcFiles[0];
File secondLrcFile = lrcFiles[1];
List<LrcEntry> mainEntryList = parseLrc(mainLrcFile);
List<LrcEntry> secondEntryList = parseLrc(secondLrcFile);
if (mainEntryList != null && secondEntryList != null) {
for (LrcEntry mainEntry : mainEntryList) {
for (LrcEntry secondEntry : secondEntryList) {
if (mainEntry.getTime() == secondEntry.getTime()) {
mainEntry.setSecondText(secondEntry.getText());
}
}
}
}
return mainEntryList;
}
/**
* 从文件解析歌词
*/
private static List<LrcEntry> parseLrc(File lrcFile) {
if (lrcFile == null || !lrcFile.exists()) {
return null;
}
List<LrcEntry> entryList = new ArrayList<>();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(lrcFile), "utf-8"));
String line;
while ((line = br.readLine()) != null) {
List<LrcEntry> list = parseLine(line);
if (list != null && !list.isEmpty()) {
entryList.addAll(list);
}
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
Collections.sort(entryList);
return entryList;
}
/**
* 从文本解析双语歌词
*/
static List<LrcEntry> parseLrc(String[] lrcTexts) {
if (lrcTexts == null || lrcTexts.length != 2 || TextUtils.isEmpty(lrcTexts[0])) {
return null;
}
String mainLrcText = lrcTexts[0];
String secondLrcText = lrcTexts[1];
List<LrcEntry> mainEntryList = parseLrc(mainLrcText);
List<LrcEntry> secondEntryList = parseLrc(secondLrcText);
if (mainEntryList != null && secondEntryList != null) {
for (LrcEntry mainEntry : mainEntryList) {
for (LrcEntry secondEntry : secondEntryList) {
if (mainEntry.getTime() == secondEntry.getTime()) {
mainEntry.setSecondText(secondEntry.getText());
}
}
}
}
return mainEntryList;
}
/**
* 从文本解析歌词
*/
private static List<LrcEntry> parseLrc(String lrcText) {
if (TextUtils.isEmpty(lrcText)) {
return null;
}
if (lrcText.startsWith("\uFEFF")) {
lrcText = lrcText.replace("\uFEFF", "");
}
List<LrcEntry> entryList = new ArrayList<>();
String[] array = lrcText.split("\\n");
for (String line : array) {
List<LrcEntry> list = parseLine(line);
if (list != null && !list.isEmpty()) {
entryList.addAll(list);
}
}
Collections.sort(entryList);
return entryList;
}
/**
* 获取网络文本需要在工作线程中执行
*/
static String getContentFromNetwork(String url, String charset) {
String lrcText = null;
try {
URL _url = new URL(url);
HttpURLConnection conn = (HttpURLConnection) _url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
if (conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
is.close();
bos.close();
lrcText = bos.toString(charset);
}
} catch (Exception e) {
e.printStackTrace();
}
return lrcText;
}
/**
* 解析一行歌词
*/
private static List<LrcEntry> parseLine(String line) {
if (TextUtils.isEmpty(line)) {
return null;
}
line = line.trim();
// [00:17.65]让我掉下眼泪的
Matcher lineMatcher = PATTERN_LINE.matcher(line);
if (!lineMatcher.matches()) {
return null;
}
String times = lineMatcher.group(1);
String text = lineMatcher.group(3);
List<LrcEntry> entryList = new ArrayList<>();
// [00:17.65]
Matcher timeMatcher = PATTERN_TIME.matcher(times);
while (timeMatcher.find()) {
long min = Long.parseLong(timeMatcher.group(1));
long sec = Long.parseLong(timeMatcher.group(2));
String milString = timeMatcher.group(3);
long mil = Long.parseLong(milString);
// 如果毫秒是两位数需要乘以10
if (milString.length() == 2) {
mil = mil * 10;
}
long time = min * DateUtils.MINUTE_IN_MILLIS + sec * DateUtils.SECOND_IN_MILLIS + mil;
entryList.add(new LrcEntry(time, text));
}
return entryList;
}
/**
* 转为[:]
*/
static String formatTime(long milli) {
int m = (int) (milli / DateUtils.MINUTE_IN_MILLIS);
int s = (int) ((milli / DateUtils.SECOND_IN_MILLIS) % 60);
String mm = String.format(Locale.getDefault(), "%02d", m);
String ss = String.format(Locale.getDefault(), "%02d", s);
return mm + ":" + ss;
}
static void resetDurationScale() {
try {
Field mField = ValueAnimator.class.getDeclaredField("sDurationScale");
mField.setAccessible(true);
mField.setFloat(null, 1);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View file

@ -15,13 +15,10 @@
package io.github.muntashirakon.music.model package io.github.muntashirakon.music.model
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import io.github.muntashirakon.music.adapter.HomeAdapter.Companion.HomeSection import io.github.muntashirakon.music.adapter.HomeAdapter.Companion.HomeSection
class Home( class Home(
val priority: Int, val arrayList: List<*>,
@StringRes val title: Int,
val arrayList: ArrayList<*>,
@HomeSection @HomeSection
val homeSection: Int, val homeSection: Int,
@DrawableRes @DrawableRes

View file

@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
import io.github.muntashirakon.music.loaders.PlaylistSongsLoader; import io.github.muntashirakon.music.loaders.PlaylistSongsLoader;
import io.github.muntashirakon.music.util.MusicUtil;
public class Playlist implements Parcelable { public class Playlist implements Parcelable {
@ -106,5 +107,14 @@ public class Playlist implements Parcelable {
dest.writeString(this.name); dest.writeString(this.name);
} }
@NonNull
public String getInfoString(@NonNull Context context) {
int songCount = getSongs(context).size();
String songCountString = MusicUtil.getSongCountString(context, songCount);
return MusicUtil.buildInfoString(
songCountString,
""
);
}
} }

View file

@ -0,0 +1,69 @@
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.PreferenceUtil;
/**
* @author SC (soncaokim)
*/
public class NotRecentlyPlayedPlaylist extends AbsSmartPlaylist {
public static final Creator<NotRecentlyPlayedPlaylist> CREATOR = new Creator<NotRecentlyPlayedPlaylist>() {
public NotRecentlyPlayedPlaylist createFromParcel(Parcel source) {
return new NotRecentlyPlayedPlaylist(source);
}
public NotRecentlyPlayedPlaylist[] newArray(int size) {
return new NotRecentlyPlayedPlaylist[size];
}
};
public NotRecentlyPlayedPlaylist(@NonNull Context context) {
super(context.getString(R.string.not_recently_played), R.drawable.ic_watch_later_white_24dp);
}
protected NotRecentlyPlayedPlaylist(Parcel in) {
super(in);
}
@NonNull
@Override
public String getInfoString(@NonNull Context context) {
String cutoff = PreferenceUtil.INSTANCE.getRecentlyPlayedCutoffText(context);
return MusicUtil.buildInfoString(
cutoff,
super.getInfoString(context)
);
}
@NonNull
@Override
public ArrayList<Song> getSongs(@NonNull Context context) {
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getNotRecentlyPlayedTracks(context);
}
@Override
public void clear(@NonNull Context context) {
}
@Override
public boolean isClearable() {
return false;
}
@Override
public int describeContents() {
return 0;
}
}

View file

@ -23,7 +23,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@ -38,7 +37,7 @@ interface GenreDetailsView : BaseView {
interface GenreDetailsPresenter : Presenter<GenreDetailsView> { interface GenreDetailsPresenter : Presenter<GenreDetailsView> {
fun loadGenreSongs(genreId: Int) fun loadGenreSongs(genreId: Int)
class GenreDetailsPresenterImpl @Inject constructor( class GenreDetailsPresenterImpl constructor(
private val repository: Repository private val repository: Repository
) : PresenterImpl<GenreDetailsView>(), GenreDetailsPresenter, CoroutineScope { ) : PresenterImpl<GenreDetailsView>(), GenreDetailsPresenter, CoroutineScope {

View file

@ -1,66 +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 io.github.muntashirakon.music.mvp.presenter
import io.github.muntashirakon.music.model.Home
import io.github.muntashirakon.music.mvp.BaseView
import io.github.muntashirakon.music.mvp.Presenter
import io.github.muntashirakon.music.mvp.PresenterImpl
import io.github.muntashirakon.music.providers.interfaces.Repository
import kotlinx.coroutines.*
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
interface HomeView : BaseView {
fun sections(sections: List<Home>)
}
interface HomePresenter : Presenter<HomeView> {
fun loadSections()
class HomePresenterImpl @Inject constructor(
private val repository: Repository
) : PresenterImpl<HomeView>(), HomePresenter, CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
override fun detachView() {
super.detachView()
job.cancel()
}
override fun loadSections() {
launch {
val list = ArrayList<Home>()
val recentArtistResult = listOf(
repository.topArtists(),
repository.topAlbums(),
repository.recentArtists(),
repository.recentAlbums(),
repository.favoritePlaylist()
)
for (r in recentArtistResult) {
r?.let { list.add(it) }
}
withContext(Dispatchers.Main) {
if (list.isNotEmpty()) view?.sections(list) else view?.showEmptyView()
}
}
}
}
}

View file

@ -24,7 +24,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@ -38,7 +38,7 @@ interface PlaylistSongsView : BaseView {
interface PlaylistSongsPresenter : Presenter<PlaylistSongsView> { interface PlaylistSongsPresenter : Presenter<PlaylistSongsView> {
fun loadPlaylistSongs(playlist: Playlist) fun loadPlaylistSongs(playlist: Playlist)
class PlaylistSongsPresenterImpl @Inject constructor( class PlaylistSongsPresenterImpl constructor(
private val repository: Repository private val repository: Repository
) : PresenterImpl<PlaylistSongsView>(), PlaylistSongsPresenter, CoroutineScope { ) : PresenterImpl<PlaylistSongsView>(), PlaylistSongsPresenter, CoroutineScope {

View file

@ -19,7 +19,7 @@ import io.github.muntashirakon.music.mvp.Presenter
import io.github.muntashirakon.music.mvp.PresenterImpl import io.github.muntashirakon.music.mvp.PresenterImpl
import io.github.muntashirakon.music.providers.interfaces.Repository import io.github.muntashirakon.music.providers.interfaces.Repository
import kotlinx.coroutines.* import kotlinx.coroutines.*
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
/** /**
@ -35,7 +35,7 @@ interface SearchPresenter : Presenter<SearchView> {
fun search(query: String?) fun search(query: String?)
class SearchPresenterImpl @Inject constructor( class SearchPresenterImpl constructor(
private val repository: Repository private val repository: Repository
) : PresenterImpl<SearchView>(), SearchPresenter, CoroutineScope { ) : PresenterImpl<SearchView>(), SearchPresenter, CoroutineScope {

View file

@ -141,6 +141,21 @@ public class HistoryStore extends SQLiteOpenHelper {
RecentStoreColumns.TIME_PLAYED + " DESC"); RecentStoreColumns.TIME_PLAYED + " DESC");
} }
public Cursor queryRecentIds(long cutoff) {
final boolean noCutoffTime = (cutoff == 0);
final boolean reverseOrder = (cutoff < 0);
if (reverseOrder) cutoff = -cutoff;
final SQLiteDatabase database = getReadableDatabase();
return database.query(RecentStoreColumns.NAME,
new String[]{RecentStoreColumns.ID},
noCutoffTime ? null : RecentStoreColumns.TIME_PLAYED + (reverseOrder ? "<?" : ">?"),
noCutoffTime ? null : new String[]{String.valueOf(cutoff)},
null, null,
RecentStoreColumns.TIME_PLAYED + (reverseOrder ? " ASC" : " DESC"));
}
public interface RecentStoreColumns { public interface RecentStoreColumns {
String NAME = "recent_history"; String NAME = "recent_history";

View file

@ -23,9 +23,9 @@ import io.github.muntashirakon.music.providers.interfaces.Repository
import io.github.muntashirakon.music.rest.LastFmClient import io.github.muntashirakon.music.rest.LastFmClient
import io.github.muntashirakon.music.rest.model.LastFmAlbum import io.github.muntashirakon.music.rest.model.LastFmAlbum
import io.github.muntashirakon.music.rest.model.LastFmArtist import io.github.muntashirakon.music.rest.model.LastFmArtist
import javax.inject.Inject import io.github.muntashirakon.music.model.smartplaylist.NotRecentlyPlayedPlaylist
class RepositoryImpl @Inject constructor(private val context: Context) : Repository { class RepositoryImpl constructor(private val context: Context) : Repository {
override suspend fun allAlbums(): List<Album> = AlbumLoader.getAllAlbums(context) override suspend fun allAlbums(): List<Album> = AlbumLoader.getAllAlbums(context)
@ -42,6 +42,18 @@ class RepositoryImpl @Inject constructor(private val context: Context) : Reposit
override suspend fun artistById(artistId: Int): Artist = override suspend fun artistById(artistId: Int): Artist =
ArtistLoader.getArtist(context, artistId) ArtistLoader.getArtist(context, artistId)
override suspend fun suggestions(): Home? {
val songs = NotRecentlyPlayedPlaylist(context).getSongs(context).shuffled().subList(0, 9)
if (songs.isNotEmpty()) {
return Home(
songs,
HomeAdapter.SUGGESTIONS,
R.drawable.ic_audiotrack_white_24dp
)
}
return null
}
override suspend fun search(query: String?): MutableList<Any> = override suspend fun search(query: String?): MutableList<Any> =
SearchLoader.searchAll(context, query) SearchLoader.searchAll(context, query)
@ -59,8 +71,6 @@ class RepositoryImpl @Inject constructor(private val context: Context) : Reposit
override suspend fun recentArtists(): Home? { override suspend fun recentArtists(): Home? {
val artists = LastAddedSongsLoader.getLastAddedArtists(context) val artists = LastAddedSongsLoader.getLastAddedArtists(context)
return if (artists.isNotEmpty()) Home( return if (artists.isNotEmpty()) Home(
0,
R.string.recent_artists,
artists, artists,
HomeAdapter.RECENT_ARTISTS, HomeAdapter.RECENT_ARTISTS,
R.drawable.ic_artist_white_24dp R.drawable.ic_artist_white_24dp
@ -69,56 +79,40 @@ class RepositoryImpl @Inject constructor(private val context: Context) : Reposit
override suspend fun recentAlbums(): Home? { override suspend fun recentAlbums(): Home? {
val albums = LastAddedSongsLoader.getLastAddedAlbums(context) val albums = LastAddedSongsLoader.getLastAddedAlbums(context)
return if (albums.isNotEmpty()) { return if (albums.isNotEmpty()) Home(
Home( albums,
1, HomeAdapter.RECENT_ALBUMS,
R.string.recent_albums, R.drawable.ic_album_white_24dp
albums, ) else null
HomeAdapter.RECENT_ALBUMS,
R.drawable.ic_album_white_24dp
)
} else null
} }
override suspend fun topAlbums(): Home? { override suspend fun topAlbums(): Home? {
val albums = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context) val albums = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
return if (albums.isNotEmpty()) { return if (albums.isNotEmpty()) Home(
Home( albums,
3, HomeAdapter.TOP_ALBUMS,
R.string.top_albums, R.drawable.ic_album_white_24dp
albums, ) else null
HomeAdapter.TOP_ALBUMS,
R.drawable.ic_album_white_24dp
)
} else null
} }
override suspend fun topArtists(): Home? { override suspend fun topArtists(): Home? {
val artists = TopAndRecentlyPlayedTracksLoader.getTopArtists(context) val artists = TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
return if (artists.isNotEmpty()) { return if (artists.isNotEmpty()) Home(
Home( artists,
2, HomeAdapter.TOP_ARTISTS,
R.string.top_artists, R.drawable.ic_artist_white_24dp
artists, ) else null
HomeAdapter.TOP_ARTISTS,
R.drawable.ic_artist_white_24dp
)
} else null
} }
override suspend fun favoritePlaylist(): Home? { override suspend fun favoritePlaylist(): Home? {
val playlists = PlaylistLoader.getFavoritePlaylist(context) val playlists = PlaylistLoader.getFavoritePlaylist(context)
return if (playlists.isNotEmpty()) { return if (playlists.isNotEmpty()) Home(
Home( playlists,
4, HomeAdapter.PLAYLISTS,
R.string.favorites, R.drawable.ic_favorite_white_24dp
playlists, ) else null
HomeAdapter.PLAYLISTS,
R.drawable.ic_favorite_white_24dp
)
} else null
} }
override suspend fun artistInfo( override suspend fun artistInfo(

View file

@ -42,6 +42,11 @@ interface Repository {
suspend fun getGenre(genreId: Int): ArrayList<Song> suspend fun getGenre(genreId: Int): ArrayList<Song>
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist
suspend fun albumInfo(artist: String, album: String): LastFmAlbum
suspend fun artistById(artistId: Int): Artist
suspend fun recentArtists(): Home? suspend fun recentArtists(): Home?
suspend fun topArtists(): Home? suspend fun topArtists(): Home?
@ -52,9 +57,5 @@ interface Repository {
suspend fun favoritePlaylist(): Home? suspend fun favoritePlaylist(): Home?
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist suspend fun suggestions(): Home?
suspend fun albumInfo(artist: String, album: String): LastFmAlbum
suspend fun artistById(artistId: Int): Artist
} }

View file

@ -127,4 +127,16 @@ public class CalendarUtil {
final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1); final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1);
return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH); return monthCal.getActualMaximum(Calendar.DAY_OF_MONTH);
} }
/**
* Returns the time elapsed so far last N days in milliseconds.
*
* @return Time elapsed since N days in milliseconds.
*/
public long getElapsedDays(int numDays) {
long elapsed = getElapsedToday();
elapsed += numDays * MS_PER_DAY;
return elapsed;
}
} }

View file

@ -72,6 +72,11 @@ public class LyricUtil {
return file.exists(); return file.exists();
} }
public static boolean isLrcOriginalFileExist(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
return file.exists();
}
@Nullable @Nullable
public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) { public static File getLocalLyricFile(@NonNull String title, @NonNull String artist) {
File file = new File(getLrcPath(title, artist)); File file = new File(getLrcPath(title, artist));
@ -82,10 +87,24 @@ public class LyricUtil {
} }
} }
@Nullable
public static File getLocalLyricOriginalFile(@NonNull String path) {
File file = new File(getLrcOriginalPath(path));
if (file.exists()) {
return file;
} else {
return null;
}
}
private static String getLrcPath(String title, String artist) { private static String getLrcPath(String title, String artist) {
return lrcRootPath + title + " - " + artist + ".lrc"; return lrcRootPath + title + " - " + artist + ".lrc";
} }
private static String getLrcOriginalPath(String filePath) {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1, filePath.length()), "lrc");
}
@NonNull @NonNull
public static String decryptBASE64(@NonNull String str) { public static String decryptBASE64(@NonNull String str) {
if (str == null || str.length() == 0) { if (str == null || str.length() == 0) {

View file

@ -44,6 +44,11 @@ import io.github.muntashirakon.music.activities.bugreport.BugReportActivity;
import io.github.muntashirakon.music.helper.MusicPlayerRemote; import io.github.muntashirakon.music.helper.MusicPlayerRemote;
import io.github.muntashirakon.music.model.Genre; import io.github.muntashirakon.music.model.Genre;
import io.github.muntashirakon.music.model.Playlist; import io.github.muntashirakon.music.model.Playlist;
import code.name.monkey.retromusic.activities.PurchaseActivity;
import code.name.monkey.retromusic.activities.SupportDevelopmentActivity;
import static io.github.muntashirakon.music.Constants.RATE_ON_GOOGLE_PLAY;
import static io.github.muntashirakon.music.util.RetroUtil.openUrl;
public class NavigationUtil { public class NavigationUtil {

View file

@ -1,5 +1,6 @@
package io.github.muntashirakon.music.util package io.github.muntashirakon.music.util
import android.content.Context
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkInfo import android.net.NetworkInfo
@ -24,6 +25,7 @@ import com.google.gson.reflect.TypeToken
import io.github.muntashirakon.music.* import io.github.muntashirakon.music.*
import java.io.File import java.io.File
object PreferenceUtil { object PreferenceUtil {
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.getContext()) private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.getContext())
@ -97,7 +99,10 @@ object PreferenceUtil {
val languageCode get() = sharedPreferences.getString(LANGUAGE_NAME, "auto") val languageCode get() = sharedPreferences.getString(LANGUAGE_NAME, "auto")
var userName var userName
get() = sharedPreferences.getString(USER_NAME, "User Name") get() = sharedPreferences.getString(
USER_NAME,
App.getContext().getString(R.string.user_name)
)
set(value) = sharedPreferences.edit { set(value) = sharedPreferences.edit {
putString(USER_NAME, value) putString(USER_NAME, value)
} }
@ -421,7 +426,7 @@ object PreferenceUtil {
var songGridSize var songGridSize
get() = sharedPreferences.getInt( get() = sharedPreferences.getInt(
SONG_GRID_SIZE, SONG_GRID_SIZE,
App.getContext().getIntRes(R.integer.default_grid_columns) App.getContext().getIntRes(R.integer.default_list_columns)
) )
set(value) = sharedPreferences.edit { set(value) = sharedPreferences.edit {
putInt(SONG_GRID_SIZE, value) putInt(SONG_GRID_SIZE, value)
@ -535,6 +540,44 @@ object PreferenceUtil {
) )
} }
fun getRecentlyPlayedCutoffTimeMillis(): Long {
return getCutoffTimeMillis(RECENTLY_PLAYED_CUTOFF)
}
fun getRecentlyPlayedCutoffText(context: Context): String? {
return getCutoffText(RECENTLY_PLAYED_CUTOFF, context)
}
private fun getCutoffText(
cutoff: String,
context: Context
): String? {
return when (sharedPreferences.getString(cutoff, "")) {
"today" -> context.getString(R.string.today)
"this_week" -> context.getString(R.string.this_week)
"past_seven_days" -> context.getString(R.string.past_seven_days)
"past_three_months" -> context.getString(R.string.past_three_months)
"this_year" -> context.getString(R.string.this_year)
"this_month" -> context.getString(R.string.this_month)
else -> context.getString(R.string.this_month)
}
}
private fun getCutoffTimeMillis(cutoff: String): Long {
val calendarUtil = CalendarUtil()
val interval: Long
interval = when (sharedPreferences.getString(cutoff, "")) {
"today" -> calendarUtil.elapsedToday
"this_week" -> calendarUtil.elapsedWeek
"past_seven_days" -> calendarUtil.getElapsedDays(7)
"past_three_months" -> calendarUtil.getElapsedMonths(3)
"this_year" -> calendarUtil.elapsedYear
"this_month" -> calendarUtil.elapsedMonth
else -> calendarUtil.elapsedMonth
}
return System.currentTimeMillis() - interval
}
val lastAddedCutoff: Long val lastAddedCutoff: Long
get() { get() {
val calendarUtil = CalendarUtil() val calendarUtil = CalendarUtil()

View file

@ -27,7 +27,6 @@ import androidx.core.view.ViewCompat
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper import code.name.monkey.appthemehelper.util.MaterialValueHelper
import com.google.android.material.slider.Slider
object ViewUtil { object ViewUtil {
@ -48,16 +47,6 @@ object ViewUtil {
} }
} }
fun setProgressDrawable(progressSlider: Slider, color: Int, thumbTint: Boolean = false) {
if (thumbTint) {
progressSlider.thumbColor = ColorStateList.valueOf(color)
}
val colorWithAlpha = ColorUtil.withAlpha(color, 0.25f)
progressSlider.haloColor = ColorStateList.valueOf(colorWithAlpha)
progressSlider.haloRadius = 0
progressSlider.trackColorActive = ColorStateList.valueOf(color)
progressSlider.trackColorInactive = ColorStateList.valueOf(colorWithAlpha)
}
fun setProgressDrawable(progressSlider: ProgressBar, newColor: Int) { fun setProgressDrawable(progressSlider: ProgressBar, newColor: Int) {

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/md_white_1000"
android:pathData="M4.27 3L3 4.27L12 13.27V13.55C11.41 13.21 10.73 13 10 13C7.79 13 6 14.79 6 17S7.79 21 10 21 14 19.21 14 17V15.27L19.73 21L21 19.73L4.27 3M14 7H18V3H12V8.18L14 10.18Z" />
</vector>

View file

@ -8,11 +8,8 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout" android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/transparent" android:layout_width="match_parent">
android:elevation="0dp"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
@ -22,7 +19,7 @@
app:contentInsetLeft="0dp" app:contentInsetLeft="0dp"
app:contentInsetStart="0dp" app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp" app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@drawable/ic_keyboard_arrow_down_black_24dp" app:navigationIcon="@drawable/ic_keyboard_backspace_black_24dp"
app:subtitleTextAppearance="@style/TextViewCaption" app:subtitleTextAppearance="@style/TextViewCaption"
app:titleMargin="0dp" app:titleMargin="0dp"
app:titleMarginStart="0dp" app:titleMarginStart="0dp"
@ -30,38 +27,15 @@
tools:subtitle="@tools:sample/full_names" tools:subtitle="@tools:sample/full_names"
tools:title="@tools:sample/full_names" /> tools:title="@tools:sample/full_names" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
style="@style/TabLayoutStyle"
android:background="@android:color/transparent"
app:tabTextAppearance="@style/TabTextAppearance">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/synced_lyrics" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/normal_lyrics" />
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager <code.name.monkey.retromusic.lyrics.LrcView
android:id="@+id/viewPager" android:id="@+id/lyricsView"
app:lrcLabel="@string/no_lyrics_found"
app:lrcPadding="16dp"
app:lrcTextGravity="left"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/fab"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="@string/edit"
app:icon="@drawable/ic_edit_white_24dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -84,6 +84,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="?attr/roundSelector" android:background="?attr/roundSelector"
app:layout_constraintBottom_toTopOf="@+id/volumeFragmentContainer" app:layout_constraintBottom_toTopOf="@+id/volumeFragmentContainer"
app:layout_constraintEnd_toStartOf="@+id/nextButton" app:layout_constraintEnd_toStartOf="@+id/nextButton"

View file

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:text="@string/suggestion_songs"
android:textAppearance="@style/TextViewHeadline6"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/card1"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card2"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card2"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card3"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card1"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image5"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card3"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card4"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card2"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image6"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card4"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card5"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card3"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image7"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card5"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card4"
app:layout_constraintTop_toBottomOf="@id/card6">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image8"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card6"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="true"
android:focusable="true"
app:cardBackgroundColor="?attr/colorSurface"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card7"
app:layout_constraintHorizontal_weight="4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:text="New music mix"
android:textAppearance="@style/TextViewHeadline4"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card7"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card8"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card6"
app:layout_constraintTop_toBottomOf="@id/title">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card8"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="4"
app:layout_constraintStart_toEndOf="@id/card7"
app:layout_constraintTop_toBottomOf="@id/title">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card9"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardUseCompatPadding="true"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toStartOf="@id/card8"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@id/card6"
app:layout_constraintTop_toBottomOf="@id/card7">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search_white_24dp"
android:title="@string/action_search"
app:showAsAction="ifRoom" />
</menu>

View file

@ -212,7 +212,7 @@
<item>sr</item> <item>sr</item>
<item>sk</item> <item>sk</item>
<item>es</item> <item>es</item>
<item>sw</item> <item>sv</item>
<item>ta</item> <item>ta</item>
<item>te</item> <item>te</item>
<item>tr</item> <item>tr</item>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="lrc_normal_text_color">#9E9E9E</color>
<color name="lrc_current_text_color">#FF4081</color>
<color name="lrc_timeline_text_color">#F8BBD0</color>
<color name="lrc_timeline_color">#809E9E9E</color>
<color name="lrc_time_text_color">#809E9E9E</color>
</resources>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="lrc_animation_duration">1000</integer>
<dimen name="lrc_text_size">16sp</dimen>
<dimen name="lrc_time_text_size">12sp</dimen>
<dimen name="lrc_divider_height">16dp</dimen>
<dimen name="lrc_timeline_height">1dp</dimen>
<dimen name="lrc_drawable_width">30dp</dimen>
<dimen name="lrc_time_width">40dp</dimen>
</resources>

View file

@ -14,25 +14,24 @@
<resources> <resources>
<declare-styleable name="LrcView"> <declare-styleable name="LrcView">
<attr name="lrcTextSize" format="dimension"/> <attr name="lrcTextSize" format="dimension" />
<attr name="lrcLineSpaceSize" format="dimension"/> <attr name="lrcNormalTextSize" format="dimension" />
<attr name="lrcNormalTextColor" format="reference|color"/> <attr name="lrcDividerHeight" format="dimension" />
<attr name="lrcCurrentTextColor" format="reference|color"/> <attr name="lrcNormalTextColor" format="reference|color" />
<attr name="lrcTouchDelay" format="integer"/> <attr name="lrcCurrentTextColor" format="reference|color" />
<attr name="noLrcTextSize" format="dimension"/> <attr name="lrcTimelineTextColor" format="reference|color" />
<attr name="noLrcTextColor" format="reference|color"/> <attr name="lrcAnimationDuration" format="integer" />
<attr name="indicatorLineHeight" format="dimension"/> <attr name="lrcLabel" format="string" />
<attr name="indicatorTextSize" format="dimension"/> <attr name="lrcPadding" format="dimension" />
<attr name="indicatorTextColor" format="reference|color"/> <attr name="lrcTimelineColor" format="reference|color" />
<attr name="currentIndicateLrcColor" format="reference|color"/> <attr name="lrcTimelineHeight" format="dimension" />
<attr name="indicatorTouchDelay" format="integer"/> <attr name="lrcPlayDrawable" format="reference" />
<attr name="indicatorLineColor" format="reference|color"/> <attr name="lrcTimeTextColor" format="reference|color" />
<attr name="indicatorStartEndMargin" format="dimension"/> <attr name="lrcTimeTextSize" format="dimension" />
<attr name="iconLineGap" format="dimension"/> <attr name="lrcTextGravity">
<attr name="playIconWidth" format="dimension"/> <enum name="center" value="0" />
<attr name="playIconHeight" format="dimension"/> <enum name="left" value="1" />
<attr name="playIcon" format="reference"/> <enum name="right" value="2" />
<attr name="isLrcCurrentTextBold" format="boolean"/> </attr>
<attr name="isLrcIndicatorTextBold" format="boolean"/>
</declare-styleable> </declare-styleable>
</resources> </resources>

View file

@ -856,6 +856,9 @@
<string name="share_summary">Share the app with your friends and family</string> <string name="share_summary">Share the app with your friends and family</string>
<string name="help_summary">Need more help?</string> <string name="help_summary">Need more help?</string>
<string name="gradient">Gradient</string> <string name="gradient">Gradient</string>
<string name="user_name">User Name</string>
<string name="not_recently_played">Not recently played</string>
<string name="past_seven_days">Past 7 days</string>
<plurals name="albumSongs"> <plurals name="albumSongs">
<item quantity="one">Song</item> <item quantity="one">Song</item>

View file

@ -4,8 +4,8 @@
android:initialLayout="@layout/app_widget_classic" android:initialLayout="@layout/app_widget_classic"
android:minWidth="@dimen/app_widget_classic_min_width" android:minWidth="@dimen/app_widget_classic_min_width"
android:minHeight="@dimen/app_widget_classic_min_height" android:minHeight="@dimen/app_widget_classic_min_height"
android:previewImage="@drawable/widget_classic"
android:resizeMode="horizontal|vertical" android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="0" android:updatePeriodMillis="0"
android:previewImage="@drawable/widget_classic"
android:widgetCategory="keyguard|home_screen" android:widgetCategory="keyguard|home_screen"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />

View file

@ -28,7 +28,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha05' implementation 'com.google.android.material:material:1.2.0-alpha05'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
// Used for the list preference classes // Used for the list preference classes
def material_dialog_version = "0.9.6.0" def material_dialog_version = "0.9.6.0"