Merge branch 'dev' of https://github.com/h4h13/RetroMusicPlayer into dev
This commit is contained in:
commit
51cdc2e9eb
50 changed files with 1754 additions and 1472 deletions
|
@ -100,9 +100,9 @@ dependencies {
|
|||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
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:converter-gson:$retrofit_version"
|
||||
|
||||
|
@ -137,10 +137,6 @@ dependencies {
|
|||
implementation 'com.heinrichreimersoftware:material-intro:1.6'
|
||||
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"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
|
|
@ -18,16 +18,12 @@ import androidx.multidex.MultiDexApplication
|
|||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||
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() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
musicComponent = initDagger(this)
|
||||
|
||||
// default theme
|
||||
if (!ThemeStore.isConfigured(this, 3)) {
|
||||
|
@ -41,18 +37,11 @@ class App : MultiDexApplication() {
|
|||
DynamicShortcutManager(this).initDynamicShortcuts()
|
||||
}
|
||||
|
||||
private fun initDagger(app: App): MusicComponent =
|
||||
DaggerMusicComponent.builder()
|
||||
.appModule(AppModule(app))
|
||||
.build()
|
||||
|
||||
companion object {
|
||||
private var instance: App? = null
|
||||
|
||||
fun getContext(): App {
|
||||
return instance!!
|
||||
}
|
||||
|
||||
lateinit var musicComponent: MusicComponent
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ import android.provider.BaseColumns
|
|||
import android.provider.MediaStore
|
||||
|
||||
object Constants {
|
||||
|
||||
const val PRO_VERSION_PRODUCT_ID = "pro_version"
|
||||
const val RATE_ON_GOOGLE_PLAY =
|
||||
"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 TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
|
||||
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 AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
|
||||
const val START_DIRECTORY = "start_directory"
|
||||
const val RECENTLY_PLAYED_CUTOFF = "recently_played_interval"
|
||||
const val LOCK_SCREEN = "lock_screen"
|
||||
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
|
||||
const val LYRICS_OPTIONS = "lyrics_tab_position"
|
||||
|
|
|
@ -8,23 +8,24 @@ import androidx.recyclerview.widget.DefaultItemAnimator
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity
|
||||
import io.github.muntashirakon.music.adapter.song.ShuffleButtonSongAdapter
|
||||
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.interfaces.CabHolder
|
||||
import io.github.muntashirakon.music.model.Genre
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
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.providers.RepositoryImpl
|
||||
import io.github.muntashirakon.music.util.DensityUtil
|
||||
import io.github.muntashirakon.music.util.RetroColorUtil
|
||||
import com.afollestad.materialcab.MaterialCab
|
||||
import kotlinx.android.synthetic.main.activity_playlist_detail.*
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* @author Hemanth S (h4h13).
|
||||
|
@ -32,9 +33,8 @@ import javax.inject.Inject
|
|||
|
||||
class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDetailsView {
|
||||
|
||||
@Inject
|
||||
lateinit var genreDetailsPresenter: GenreDetailsPresenter
|
||||
|
||||
private lateinit var genreDetailsPresenter: GenreDetailsPresenter
|
||||
private lateinit var genre: Genre
|
||||
private lateinit var songAdapter: ShuffleButtonSongAdapter
|
||||
private var cab: MaterialCab? = null
|
||||
|
@ -62,16 +62,14 @@ class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDet
|
|||
setTaskDescriptionColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
setBottomBarVisibility(View.GONE)
|
||||
if (intent.extras != null) {
|
||||
genre = intent?.extras?.getParcelable(EXTRA_GENRE_ID)!!
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
|
||||
genre = extraNotNull<Genre>(EXTRA_GENRE_ID).value
|
||||
|
||||
setUpToolBar()
|
||||
setupRecyclerView()
|
||||
|
||||
App.musicComponent.inject(this)
|
||||
genreDetailsPresenter =
|
||||
GenreDetailsPresenterImpl(RepositoryImpl(this))
|
||||
genreDetailsPresenter.attachView(this)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,72 +1,27 @@
|
|||
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.text.TextUtils
|
||||
import android.view.*
|
||||
import androidx.annotation.StringRes
|
||||
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 android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
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 io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
|
||||
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.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.lyrics.Lyrics
|
||||
import io.github.muntashirakon.music.util.LyricUtil
|
||||
import io.github.muntashirakon.music.util.MusicUtil
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
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,
|
||||
ViewPager.OnPageChangeListener {
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
when (state) {
|
||||
ViewPager.SCROLL_STATE_IDLE -> fab.show()
|
||||
ViewPager.SCROLL_STATE_DRAGGING,
|
||||
ViewPager.SCROLL_STATE_SETTLING -> fab.hide()
|
||||
}
|
||||
}
|
||||
class LyricsActivity : AbsMusicServiceActivity(), MusicProgressViewUpdateHelper.Callback {
|
||||
private lateinit var updateHelper: MusicProgressViewUpdateHelper
|
||||
|
||||
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 var lyricsString: String? = null
|
||||
|
||||
private val googleSearchLrcUrl: String
|
||||
get() {
|
||||
|
@ -84,56 +39,63 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener,
|
|||
setTaskDescriptionColorAuto()
|
||||
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()
|
||||
|
||||
viewPager.apply {
|
||||
adapter = PagerAdapter(supportFragmentManager)
|
||||
currentItem = PreferenceUtil.lyricsOption
|
||||
addOnPageChangeListener(this@LyricsActivity)
|
||||
}
|
||||
|
||||
|
||||
toolbar.setBackgroundColor(surfaceColor())
|
||||
tabs.setBackgroundColor(surfaceColor())
|
||||
ToolbarContentTintHelper.colorBackButton(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() {
|
||||
super.onPlayingMetaChanged()
|
||||
updateTitleSong()
|
||||
loadLRCLyrics()
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
updateTitleSong()
|
||||
loadLRCLyrics()
|
||||
}
|
||||
|
||||
private fun updateTitleSong() {
|
||||
|
@ -146,269 +108,19 @@ class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener,
|
|||
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 {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
if (item.itemId == R.id.action_search) {
|
||||
RetroUtil.openUrl(this, googleSearchLrcUrl)
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,13 +7,13 @@ import android.view.View
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.base.AbsSlidingMusicPanelActivity
|
||||
import io.github.muntashirakon.music.adapter.song.OrderablePlaylistSongAdapter
|
||||
import io.github.muntashirakon.music.adapter.song.PlaylistSongAdapter
|
||||
import io.github.muntashirakon.music.adapter.song.SongAdapter
|
||||
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.interfaces.CabHolder
|
||||
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.Song
|
||||
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.providers.RepositoryImpl
|
||||
import io.github.muntashirakon.music.util.DensityUtil
|
||||
import io.github.muntashirakon.music.util.PlaylistsUtil
|
||||
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.utils.WrapperAdapterUtils
|
||||
import kotlinx.android.synthetic.main.activity_playlist_detail.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsView {
|
||||
|
||||
@Inject
|
||||
lateinit var playlistSongsPresenter: PlaylistSongsPresenter
|
||||
|
||||
private lateinit var presenter: PlaylistSongsPresenter
|
||||
private lateinit var playlist: Playlist
|
||||
private var cab: MaterialCab? = null
|
||||
private lateinit var adapter: SongAdapter
|
||||
|
@ -52,14 +52,10 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
|
|||
setLightNavigationBar(true)
|
||||
setBottomBarVisibility(View.GONE)
|
||||
|
||||
App.musicComponent.inject(this)
|
||||
playlistSongsPresenter.attachView(this)
|
||||
presenter = PlaylistSongsPresenterImpl(RepositoryImpl(this))
|
||||
presenter.attachView(this)
|
||||
|
||||
if (intent.extras != null) {
|
||||
playlist = intent.extras!!.getParcelable(EXTRA_PLAYLIST)!!
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
playlist = extraNotNull<Playlist>(EXTRA_PLAYLIST).value
|
||||
|
||||
setUpToolBar()
|
||||
setUpRecyclerView()
|
||||
|
@ -114,7 +110,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
playlistSongsPresenter.loadPlaylistSongs(playlist)
|
||||
presenter.loadPlaylistSongs(playlist)
|
||||
}
|
||||
|
||||
private fun setUpToolBar() {
|
||||
|
@ -181,7 +177,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
|
|||
setToolbarTitle(playlist.name)
|
||||
}
|
||||
}
|
||||
playlistSongsPresenter.loadPlaylistSongs(playlist)
|
||||
presenter.loadPlaylistSongs(playlist)
|
||||
}
|
||||
|
||||
private fun setToolbarTitle(title: String) {
|
||||
|
@ -227,7 +223,7 @@ class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, Playli
|
|||
wrappedAdapter = null
|
||||
}
|
||||
super.onDestroy()
|
||||
playlistSongsPresenter.detachView()
|
||||
presenter.detachView()
|
||||
}
|
||||
|
||||
override fun showEmptyView() {
|
||||
|
|
|
@ -21,23 +21,22 @@ import code.name.monkey.appthemehelper.ThemeStore
|
|||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.activities.base.AbsMusicServiceActivity
|
||||
import io.github.muntashirakon.music.adapter.SearchAdapter
|
||||
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.providers.RepositoryImpl
|
||||
import io.github.muntashirakon.music.util.RetroUtil
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import kotlinx.android.synthetic.main.activity_search.*
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatcher, SearchView {
|
||||
@Inject
|
||||
lateinit var searchPresenter: SearchPresenter
|
||||
|
||||
private lateinit var presenter: SearchPresenter
|
||||
private var searchAdapter: SearchAdapter? = null
|
||||
private var query: String? = null
|
||||
|
||||
|
@ -50,8 +49,8 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
|
|||
setTaskDescriptionColorAuto()
|
||||
setLightNavigationBar(true)
|
||||
|
||||
App.musicComponent.inject(this)
|
||||
searchPresenter.attachView(this)
|
||||
presenter = SearchPresenterImpl(RepositoryImpl(this))
|
||||
presenter.attachView(this)
|
||||
|
||||
setupRecyclerView()
|
||||
setUpToolBar()
|
||||
|
@ -117,7 +116,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
searchPresenter.detachView()
|
||||
presenter.detachView()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -134,7 +133,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
|
|||
TransitionManager.beginDelayedTransition(appBarLayout)
|
||||
voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE
|
||||
clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE
|
||||
searchPresenter.search(query)
|
||||
presenter.search(query)
|
||||
}
|
||||
|
||||
override fun onMediaStoreChanged() {
|
||||
|
@ -176,7 +175,7 @@ class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatch
|
|||
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
|
||||
query = result?.get(0)
|
||||
searchView.setText(query, BufferType.EDITABLE)
|
||||
searchPresenter.search(query!!)
|
||||
presenter.search(query!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ class UserInfoActivity : AbsBaseActivity() {
|
|||
next.setOnClickListener {
|
||||
val nameString = name.text.toString().trim { it <= ' ' }
|
||||
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
|
||||
}
|
||||
PreferenceUtil.userName = nameString
|
||||
|
|
|
@ -4,24 +4,26 @@ import android.util.DisplayMetrics
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.appthemehelper.ThemeStore
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.adapter.album.AlbumFullWidthAdapter
|
||||
import io.github.muntashirakon.music.adapter.artist.ArtistAdapter
|
||||
import io.github.muntashirakon.music.adapter.song.SongAdapter
|
||||
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.model.Album
|
||||
import io.github.muntashirakon.music.model.Artist
|
||||
import io.github.muntashirakon.music.model.Home
|
||||
import io.github.muntashirakon.music.model.*
|
||||
import io.github.muntashirakon.music.model.Playlist
|
||||
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import com.bumptech.glide.Glide
|
||||
|
||||
class HomeAdapter(
|
||||
private val activity: AppCompatActivity,
|
||||
|
@ -40,6 +42,15 @@ class HomeAdapter(
|
|||
return when (viewType) {
|
||||
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
|
||||
PLAYLISTS -> PlaylistViewHolder(layout)
|
||||
SUGGESTIONS -> {
|
||||
SuggestionsViewHolder(
|
||||
LayoutInflater.from(activity).inflate(
|
||||
R.layout.item_suggestions,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
AlbumViewHolder(
|
||||
LayoutInflater.from(activity).inflate(
|
||||
|
@ -56,23 +67,41 @@ class HomeAdapter(
|
|||
when (getItemViewType(position)) {
|
||||
RECENT_ALBUMS -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 {
|
||||
|
||||
@IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS)
|
||||
@IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS, SUGGESTIONS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class HomeSection
|
||||
|
||||
|
@ -96,11 +125,12 @@ class HomeAdapter(
|
|||
const val TOP_ALBUMS = 1
|
||||
const val RECENT_ARTISTS = 2
|
||||
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) {
|
||||
fun bindView(list: ArrayList<Album>, titleRes: Int) {
|
||||
fun bindView(list: List<Album>, titleRes: Int) {
|
||||
if (list.isNotEmpty()) {
|
||||
recyclerView.apply {
|
||||
show()
|
||||
|
@ -112,7 +142,7 @@ class HomeAdapter(
|
|||
}
|
||||
|
||||
inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
|
||||
fun bindView(list: ArrayList<Artist>, titleRes: Int) {
|
||||
fun bindView(list: List<Artist>, titleRes: Int) {
|
||||
if (list.isNotEmpty()) {
|
||||
val manager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
|
||||
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) {
|
||||
fun bindView(arrayList: ArrayList<Playlist>, titleRes: Int) {
|
||||
fun bindView(arrayList: List<Playlist>, titleRes: Int) {
|
||||
if (arrayList.isNotEmpty()) {
|
||||
val songs = PlaylistSongsLoader.getPlaylistSongList(activity, arrayList[0])
|
||||
if (songs.isNotEmpty()) {
|
||||
|
@ -155,28 +214,3 @@ class HomeAdapter(
|
|||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -14,11 +14,16 @@ import io.github.muntashirakon.music.glide.RetroMusicColoredTarget
|
|||
import io.github.muntashirakon.music.glide.SongGlideRequest
|
||||
import io.github.muntashirakon.music.misc.CustomFragmentStatePagerAdapter
|
||||
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.PreferenceUtil
|
||||
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
|
||||
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(
|
||||
fragmentManager: FragmentManager,
|
||||
|
@ -85,20 +90,33 @@ class AlbumCoverPagerAdapter(
|
|||
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
|
||||
albumCover = view.findViewById(R.id.player_image)
|
||||
albumCover.setOnClickListener {
|
||||
NavigationUtil.goToLyrics(requireActivity())
|
||||
showLyricsDialog()
|
||||
}
|
||||
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 {
|
||||
return when (PreferenceUtil.nowPlayingScreen) {
|
||||
Card,
|
||||
Fit,
|
||||
Tiny,
|
||||
Classic,
|
||||
Peak,
|
||||
Gradient,
|
||||
Full -> R.layout.fragment_album_full_cover
|
||||
Card, Fit, Tiny, Classic, Peak, Gradient, Full -> R.layout.fragment_album_full_cover
|
||||
else -> {
|
||||
if (PreferenceUtil.isCarouselEffect) {
|
||||
R.layout.fragment_album_carousel_cover
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -55,7 +55,8 @@ class LibraryViewModel(application: Application) :
|
|||
_repository.topAlbums(),
|
||||
_repository.recentArtists(),
|
||||
_repository.recentAlbums(),
|
||||
_repository.favoritePlaylist()
|
||||
_repository.favoritePlaylist(),
|
||||
_repository.suggestions()
|
||||
)
|
||||
for (r in result) {
|
||||
if (r != null) {
|
||||
|
|
|
@ -18,7 +18,6 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.github.muntashirakon.music.App
|
||||
import io.github.muntashirakon.music.R
|
||||
import io.github.muntashirakon.music.adapter.GenreAdapter
|
||||
import io.github.muntashirakon.music.fragments.base.AbsLibraryPagerRecyclerViewFragment
|
||||
|
@ -27,11 +26,6 @@ import io.github.muntashirakon.music.interfaces.MainActivityFragmentCallbacks
|
|||
class GenresFragment : AbsLibraryPagerRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
|
||||
MainActivityFragmentCallbacks {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
App.musicComponent.inject(this)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
mainActivity.libraryViewModel.allGenres().observe(
|
||||
|
|
|
@ -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
|
||||
get() {
|
||||
val display = mainActivity.windowManager.defaultDisplay
|
||||
|
@ -111,8 +104,7 @@ class BannerHomeFragment : AbsMainActivityFragment(), MainActivityFragmentCallba
|
|||
)
|
||||
NavigationUtil.goToUserInfo(requireActivity(), options)
|
||||
}
|
||||
titleWelcome?.text =
|
||||
String.format("%s", PreferenceUtil.userName)
|
||||
titleWelcome?.text = String.format("%s", PreferenceUtil.userName)
|
||||
|
||||
homeAdapter = HomeAdapter(mainActivity, displayMetrics)
|
||||
recyclerView.apply {
|
||||
|
|
|
@ -33,7 +33,6 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(), ViewPager.OnPageChan
|
|||
fun removeSlideEffect() {
|
||||
val transformer = ParallaxPagerTransformer(R.id.player_image)
|
||||
transformer.setSpeed(0.3f)
|
||||
//viewPager.setPageTransformer(true, transformer)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
|
@ -4,8 +4,6 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
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.player.PlayerAlbumCoverFragment
|
||||
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.lyrics.AbsSynchronizedLyrics
|
||||
import io.github.muntashirakon.music.model.lyrics.Lyrics
|
||||
import io.github.muntashirakon.music.util.color.MediaNotificationProcessor
|
||||
import kotlinx.android.synthetic.main.fragment_adaptive_player.*
|
||||
|
||||
class AdaptiveFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Callback {
|
||||
|
||||
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()
|
||||
}
|
||||
class AdaptiveFragment : AbsPlayerFragment() {
|
||||
|
||||
override fun playerToolbar(): Toolbar {
|
||||
return playerToolbar
|
||||
|
@ -132,15 +37,8 @@ class AdaptiveFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelper.Call
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
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()
|
||||
setUpPlayerToolbar()
|
||||
|
||||
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this, 500, 1000)
|
||||
progressViewUpdateHelper.start()
|
||||
}
|
||||
|
||||
private fun setUpSubFragments() {
|
||||
|
|
|
@ -279,6 +279,10 @@ class GradientPlayerFragment : AbsPlayerFragment(), MusicProgressViewUpdateHelpe
|
|||
updateQueuePosition()
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
super.onQueueChanged()
|
||||
updateLabel()
|
||||
}
|
||||
private fun updateSong() {
|
||||
val song = MusicPlayerRemote.currentSong
|
||||
title.text = song.title
|
||||
|
|
|
@ -17,14 +17,18 @@ package io.github.muntashirakon.music.loaders
|
|||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.provider.BaseColumns
|
||||
import android.provider.MediaStore
|
||||
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.Artist
|
||||
import io.github.muntashirakon.music.model.Song
|
||||
import io.github.muntashirakon.music.providers.HistoryStore
|
||||
import io.github.muntashirakon.music.providers.SongPlayCountStore
|
||||
import io.github.muntashirakon.music.util.PreferenceUtil
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Created by hemanths on 16/08/17.
|
||||
*/
|
||||
|
@ -39,19 +43,23 @@ object TopAndRecentlyPlayedTracksLoader {
|
|||
return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context))
|
||||
}
|
||||
|
||||
private fun makeRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? {
|
||||
val retCursor = makeRecentTracksCursorImpl(context)
|
||||
|
||||
// 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
|
||||
fun getNotRecentlyPlayedTracks(context: Context): ArrayList<Song> {
|
||||
val allSongs = SongLoader.getSongs(
|
||||
makeSongCursor(
|
||||
context,
|
||||
null, null,
|
||||
MediaStore.Audio.Media.DATE_ADDED + " ASC"
|
||||
)
|
||||
)
|
||||
val playedSongs = SongLoader.getSongs(
|
||||
makePlayedTracksCursorAndClearUpDatabase(context)
|
||||
)
|
||||
val notRecentlyPlayedSongs = SongLoader.getSongs(
|
||||
makeNotRecentTracksCursorAndClearUpDatabase(context)
|
||||
)
|
||||
allSongs.removeAll(playedSongs)
|
||||
allSongs.addAll(notRecentlyPlayedSongs)
|
||||
return allSongs
|
||||
}
|
||||
|
||||
private fun makeTopTracksCursorAndClearUpDatabase(context: Context): Cursor? {
|
||||
|
@ -72,21 +80,19 @@ object TopAndRecentlyPlayedTracksLoader {
|
|||
private fun makeRecentTracksCursorImpl(context: Context): SortedLongCursor? {
|
||||
// first get the top results ids from the internal database
|
||||
val songs = HistoryStore.getInstance(context).queryRecentIds()
|
||||
|
||||
try {
|
||||
songs.use {
|
||||
return makeSortedCursor(
|
||||
context, songs,
|
||||
songs!!.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
|
||||
context,
|
||||
it,
|
||||
it.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
|
||||
)
|
||||
} finally {
|
||||
songs?.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeTopTracksCursorImpl(context: Context): SortedLongCursor? {
|
||||
// first get the top results ids from the internal database
|
||||
val songs = SongPlayCountStore.getInstance(context)
|
||||
.getTopPlayedResults(NUMBER_OF_TOP_TRACKS)
|
||||
val songs =
|
||||
SongPlayCountStore.getInstance(context).getTopPlayedResults(NUMBER_OF_TOP_TRACKS)
|
||||
|
||||
songs.use { localSongs ->
|
||||
return makeSortedCursor(
|
||||
|
@ -145,4 +151,57 @@ object TopAndRecentlyPlayedTracksLoader {
|
|||
fun getTopArtists(context: Context): ArrayList<Artist> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -15,13 +15,10 @@
|
|||
package io.github.muntashirakon.music.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import io.github.muntashirakon.music.adapter.HomeAdapter.Companion.HomeSection
|
||||
|
||||
class Home(
|
||||
val priority: Int,
|
||||
@StringRes val title: Int,
|
||||
val arrayList: ArrayList<*>,
|
||||
val arrayList: List<*>,
|
||||
@HomeSection
|
||||
val homeSection: Int,
|
||||
@DrawableRes
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
|
|||
import java.util.ArrayList;
|
||||
|
||||
import io.github.muntashirakon.music.loaders.PlaylistSongsLoader;
|
||||
import io.github.muntashirakon.music.util.MusicUtil;
|
||||
|
||||
|
||||
public class Playlist implements Parcelable {
|
||||
|
@ -106,5 +107,14 @@ public class Playlist implements Parcelable {
|
|||
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,
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
|
@ -38,7 +37,7 @@ interface GenreDetailsView : BaseView {
|
|||
interface GenreDetailsPresenter : Presenter<GenreDetailsView> {
|
||||
fun loadGenreSongs(genreId: Int)
|
||||
|
||||
class GenreDetailsPresenterImpl @Inject constructor(
|
||||
class GenreDetailsPresenterImpl constructor(
|
||||
private val repository: Repository
|
||||
) : PresenterImpl<GenreDetailsView>(), GenreDetailsPresenter, CoroutineScope {
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
|
@ -38,7 +38,7 @@ interface PlaylistSongsView : BaseView {
|
|||
interface PlaylistSongsPresenter : Presenter<PlaylistSongsView> {
|
||||
fun loadPlaylistSongs(playlist: Playlist)
|
||||
|
||||
class PlaylistSongsPresenterImpl @Inject constructor(
|
||||
class PlaylistSongsPresenterImpl constructor(
|
||||
private val repository: Repository
|
||||
) : PresenterImpl<PlaylistSongsView>(), PlaylistSongsPresenter, CoroutineScope {
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ 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
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,7 @@ interface SearchPresenter : Presenter<SearchView> {
|
|||
|
||||
fun search(query: String?)
|
||||
|
||||
class SearchPresenterImpl @Inject constructor(
|
||||
class SearchPresenterImpl constructor(
|
||||
private val repository: Repository
|
||||
) : PresenterImpl<SearchView>(), SearchPresenter, CoroutineScope {
|
||||
|
||||
|
|
|
@ -141,6 +141,21 @@ public class HistoryStore extends SQLiteOpenHelper {
|
|||
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 {
|
||||
String NAME = "recent_history";
|
||||
|
||||
|
|
|
@ -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.model.LastFmAlbum
|
||||
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)
|
||||
|
||||
|
@ -42,6 +42,18 @@ class RepositoryImpl @Inject constructor(private val context: Context) : Reposit
|
|||
override suspend fun artistById(artistId: Int): Artist =
|
||||
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> =
|
||||
SearchLoader.searchAll(context, query)
|
||||
|
||||
|
@ -59,8 +71,6 @@ class RepositoryImpl @Inject constructor(private val context: Context) : Reposit
|
|||
override suspend fun recentArtists(): Home? {
|
||||
val artists = LastAddedSongsLoader.getLastAddedArtists(context)
|
||||
return if (artists.isNotEmpty()) Home(
|
||||
0,
|
||||
R.string.recent_artists,
|
||||
artists,
|
||||
HomeAdapter.RECENT_ARTISTS,
|
||||
R.drawable.ic_artist_white_24dp
|
||||
|
@ -69,56 +79,40 @@ class RepositoryImpl @Inject constructor(private val context: Context) : Reposit
|
|||
|
||||
override suspend fun recentAlbums(): Home? {
|
||||
val albums = LastAddedSongsLoader.getLastAddedAlbums(context)
|
||||
return if (albums.isNotEmpty()) {
|
||||
Home(
|
||||
1,
|
||||
R.string.recent_albums,
|
||||
albums,
|
||||
HomeAdapter.RECENT_ALBUMS,
|
||||
R.drawable.ic_album_white_24dp
|
||||
)
|
||||
} else null
|
||||
return if (albums.isNotEmpty()) Home(
|
||||
albums,
|
||||
HomeAdapter.RECENT_ALBUMS,
|
||||
R.drawable.ic_album_white_24dp
|
||||
) else null
|
||||
}
|
||||
|
||||
override suspend fun topAlbums(): Home? {
|
||||
val albums = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
|
||||
return if (albums.isNotEmpty()) {
|
||||
Home(
|
||||
3,
|
||||
R.string.top_albums,
|
||||
albums,
|
||||
HomeAdapter.TOP_ALBUMS,
|
||||
R.drawable.ic_album_white_24dp
|
||||
)
|
||||
} else null
|
||||
return if (albums.isNotEmpty()) Home(
|
||||
albums,
|
||||
HomeAdapter.TOP_ALBUMS,
|
||||
R.drawable.ic_album_white_24dp
|
||||
) else null
|
||||
}
|
||||
|
||||
override suspend fun topArtists(): Home? {
|
||||
|
||||
val artists = TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
|
||||
return if (artists.isNotEmpty()) {
|
||||
Home(
|
||||
2,
|
||||
R.string.top_artists,
|
||||
artists,
|
||||
HomeAdapter.TOP_ARTISTS,
|
||||
R.drawable.ic_artist_white_24dp
|
||||
)
|
||||
} else null
|
||||
return if (artists.isNotEmpty()) Home(
|
||||
artists,
|
||||
HomeAdapter.TOP_ARTISTS,
|
||||
R.drawable.ic_artist_white_24dp
|
||||
) else null
|
||||
|
||||
}
|
||||
|
||||
override suspend fun favoritePlaylist(): Home? {
|
||||
val playlists = PlaylistLoader.getFavoritePlaylist(context)
|
||||
return if (playlists.isNotEmpty()) {
|
||||
Home(
|
||||
4,
|
||||
R.string.favorites,
|
||||
playlists,
|
||||
HomeAdapter.PLAYLISTS,
|
||||
R.drawable.ic_favorite_white_24dp
|
||||
)
|
||||
} else null
|
||||
return if (playlists.isNotEmpty()) Home(
|
||||
playlists,
|
||||
HomeAdapter.PLAYLISTS,
|
||||
R.drawable.ic_favorite_white_24dp
|
||||
) else null
|
||||
}
|
||||
|
||||
override suspend fun artistInfo(
|
||||
|
|
|
@ -42,6 +42,11 @@ interface Repository {
|
|||
|
||||
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 topArtists(): Home?
|
||||
|
@ -52,9 +57,5 @@ interface Repository {
|
|||
|
||||
suspend fun favoritePlaylist(): Home?
|
||||
|
||||
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 suggestions(): Home?
|
||||
}
|
|
@ -127,4 +127,16 @@ public class CalendarUtil {
|
|||
final Calendar monthCal = new GregorianCalendar(calendar.get(Calendar.YEAR), month, 1);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,11 @@ public class LyricUtil {
|
|||
return file.exists();
|
||||
}
|
||||
|
||||
public static boolean isLrcOriginalFileExist(@NonNull String path) {
|
||||
File file = new File(getLrcOriginalPath(path));
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getLocalLyricFile(@NonNull String title, @NonNull String 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) {
|
||||
return lrcRootPath + title + " - " + artist + ".lrc";
|
||||
}
|
||||
|
||||
private static String getLrcOriginalPath(String filePath) {
|
||||
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1, filePath.length()), "lrc");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String decryptBASE64(@NonNull String str) {
|
||||
if (str == null || str.length() == 0) {
|
||||
|
|
|
@ -44,6 +44,11 @@ import io.github.muntashirakon.music.activities.bugreport.BugReportActivity;
|
|||
import io.github.muntashirakon.music.helper.MusicPlayerRemote;
|
||||
import io.github.muntashirakon.music.model.Genre;
|
||||
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 {
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.github.muntashirakon.music.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkInfo
|
||||
|
@ -24,6 +25,7 @@ import com.google.gson.reflect.TypeToken
|
|||
import io.github.muntashirakon.music.*
|
||||
import java.io.File
|
||||
|
||||
|
||||
object PreferenceUtil {
|
||||
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(App.getContext())
|
||||
|
||||
|
@ -97,7 +99,10 @@ object PreferenceUtil {
|
|||
val languageCode get() = sharedPreferences.getString(LANGUAGE_NAME, "auto")
|
||||
|
||||
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 {
|
||||
putString(USER_NAME, value)
|
||||
}
|
||||
|
@ -421,7 +426,7 @@ object PreferenceUtil {
|
|||
var songGridSize
|
||||
get() = sharedPreferences.getInt(
|
||||
SONG_GRID_SIZE,
|
||||
App.getContext().getIntRes(R.integer.default_grid_columns)
|
||||
App.getContext().getIntRes(R.integer.default_list_columns)
|
||||
)
|
||||
set(value) = sharedPreferences.edit {
|
||||
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
|
||||
get() {
|
||||
val calendarUtil = CalendarUtil()
|
||||
|
|
|
@ -27,7 +27,6 @@ import androidx.core.view.ViewCompat
|
|||
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
||||
import com.google.android.material.slider.Slider
|
||||
|
||||
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) {
|
||||
|
||||
|
|
9
app/src/main/res/drawable/ic_watch_later_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_watch_later_white_24dp.xml
Normal 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>
|
|
@ -8,11 +8,8 @@
|
|||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:elevation="0dp"
|
||||
app:elevation="0dp">
|
||||
android:layout_width="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
@ -22,7 +19,7 @@
|
|||
app:contentInsetLeft="0dp"
|
||||
app:contentInsetStart="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:titleMargin="0dp"
|
||||
app:titleMarginStart="0dp"
|
||||
|
@ -30,38 +27,15 @@
|
|||
tools:subtitle="@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>
|
||||
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/viewPager"
|
||||
<code.name.monkey.retromusic.lyrics.LrcView
|
||||
android:id="@+id/lyricsView"
|
||||
app:lrcLabel="@string/no_lyrics_found"
|
||||
app:lrcPadding="16dp"
|
||||
app:lrcTextGravity="left"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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>
|
|
@ -84,6 +84,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="?attr/roundSelector"
|
||||
app:layout_constraintBottom_toTopOf="@+id/volumeFragmentContainer"
|
||||
app:layout_constraintEnd_toStartOf="@+id/nextButton"
|
||||
|
|
207
app/src/main/res/layout/item_suggestions.xml
Normal file
207
app/src/main/res/layout/item_suggestions.xml
Normal 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>
|
9
app/src/main/res/menu/menu_search.xml
Normal file
9
app/src/main/res/menu/menu_search.xml
Normal 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>
|
|
@ -212,7 +212,7 @@
|
|||
<item>sr</item>
|
||||
<item>sk</item>
|
||||
<item>es</item>
|
||||
<item>sw</item>
|
||||
<item>sv</item>
|
||||
<item>ta</item>
|
||||
<item>te</item>
|
||||
<item>tr</item>
|
||||
|
|
8
app/src/main/res/values/lrc_colors.xml
Normal file
8
app/src/main/res/values/lrc_colors.xml
Normal 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>
|
10
app/src/main/res/values/lrc_dimens.xml
Normal file
10
app/src/main/res/values/lrc_dimens.xml
Normal 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>
|
|
@ -14,25 +14,24 @@
|
|||
|
||||
<resources>
|
||||
<declare-styleable name="LrcView">
|
||||
<attr name="lrcTextSize" format="dimension"/>
|
||||
<attr name="lrcLineSpaceSize" format="dimension"/>
|
||||
<attr name="lrcNormalTextColor" format="reference|color"/>
|
||||
<attr name="lrcCurrentTextColor" format="reference|color"/>
|
||||
<attr name="lrcTouchDelay" format="integer"/>
|
||||
<attr name="noLrcTextSize" format="dimension"/>
|
||||
<attr name="noLrcTextColor" format="reference|color"/>
|
||||
<attr name="indicatorLineHeight" format="dimension"/>
|
||||
<attr name="indicatorTextSize" format="dimension"/>
|
||||
<attr name="indicatorTextColor" format="reference|color"/>
|
||||
<attr name="currentIndicateLrcColor" format="reference|color"/>
|
||||
<attr name="indicatorTouchDelay" format="integer"/>
|
||||
<attr name="indicatorLineColor" format="reference|color"/>
|
||||
<attr name="indicatorStartEndMargin" format="dimension"/>
|
||||
<attr name="iconLineGap" format="dimension"/>
|
||||
<attr name="playIconWidth" format="dimension"/>
|
||||
<attr name="playIconHeight" format="dimension"/>
|
||||
<attr name="playIcon" format="reference"/>
|
||||
<attr name="isLrcCurrentTextBold" format="boolean"/>
|
||||
<attr name="isLrcIndicatorTextBold" format="boolean"/>
|
||||
<attr name="lrcTextSize" format="dimension" />
|
||||
<attr name="lrcNormalTextSize" format="dimension" />
|
||||
<attr name="lrcDividerHeight" format="dimension" />
|
||||
<attr name="lrcNormalTextColor" format="reference|color" />
|
||||
<attr name="lrcCurrentTextColor" format="reference|color" />
|
||||
<attr name="lrcTimelineTextColor" format="reference|color" />
|
||||
<attr name="lrcAnimationDuration" format="integer" />
|
||||
<attr name="lrcLabel" format="string" />
|
||||
<attr name="lrcPadding" format="dimension" />
|
||||
<attr name="lrcTimelineColor" format="reference|color" />
|
||||
<attr name="lrcTimelineHeight" format="dimension" />
|
||||
<attr name="lrcPlayDrawable" format="reference" />
|
||||
<attr name="lrcTimeTextColor" format="reference|color" />
|
||||
<attr name="lrcTimeTextSize" format="dimension" />
|
||||
<attr name="lrcTextGravity">
|
||||
<enum name="center" value="0" />
|
||||
<enum name="left" value="1" />
|
||||
<enum name="right" value="2" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
|
@ -856,6 +856,9 @@
|
|||
<string name="share_summary">Share the app with your friends and family</string>
|
||||
<string name="help_summary">Need more help?</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">
|
||||
<item quantity="one">Song</item>
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
android:initialLayout="@layout/app_widget_classic"
|
||||
android:minWidth="@dimen/app_widget_classic_min_width"
|
||||
android:minHeight="@dimen/app_widget_classic_min_height"
|
||||
android:previewImage="@drawable/widget_classic"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:previewImage="@drawable/widget_classic"
|
||||
android:widgetCategory="keyguard|home_screen"
|
||||
tools:ignore="UnusedAttribute" />
|
|
@ -28,7 +28,7 @@ dependencies {
|
|||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
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'
|
||||
// Used for the list preference classes
|
||||
def material_dialog_version = "0.9.6.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue