Refactor packages

This commit is contained in:
h4h13 2019-04-20 10:59:45 +05:30
parent c386e1fe93
commit 39b0b4c931
188 changed files with 440 additions and 835 deletions

View file

@ -0,0 +1,179 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.core.app.ShareCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.Constants.APP_INSTAGRAM_LINK
import code.name.monkey.retromusic.Constants.APP_TELEGRAM_LINK
import code.name.monkey.retromusic.Constants.APP_TWITTER_LINK
import code.name.monkey.retromusic.Constants.DISCORD_LINK
import code.name.monkey.retromusic.Constants.FAQ_LINK
import code.name.monkey.retromusic.Constants.GITHUB_PROJECT
import code.name.monkey.retromusic.Constants.PINTEREST
import code.name.monkey.retromusic.Constants.RATE_ON_GOOGLE_PLAY
import code.name.monkey.retromusic.Constants.TELEGRAM_CHANGE_LOG
import code.name.monkey.retromusic.Constants.TRANSLATE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Contributor
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.adapter.ContributorAdapter
import code.name.monkey.retromusic.util.NavigationUtil
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.android.synthetic.main.activity_about.*
import kotlinx.android.synthetic.main.card_credit.*
import kotlinx.android.synthetic.main.card_other.*
import kotlinx.android.synthetic.main.card_retro_info.*
import kotlinx.android.synthetic.main.card_social.*
import java.io.IOException
import java.nio.charset.StandardCharsets
class AboutActivity : AbsBaseActivity(), View.OnClickListener {
private val assetJsonData: String?
get() {
val json: String
try {
val inputStream = assets.open("contributors.json")
val size = inputStream.available()
val buffer = ByteArray(size)
inputStream.read(buffer)
inputStream.close()
json = String(buffer, StandardCharsets.UTF_8)
} catch (ex: IOException) {
ex.printStackTrace()
return null
}
return json
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
loadContributors()
setUpToolbar()
appVersion.text = getAppVersion()
setUpView()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun setUpToolbar() {
appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this))
toolbar.setBackgroundColor(ThemeStore.primaryColor(this))
setSupportActionBar(toolbar)
title = null
ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.textColorSecondary(this))
}
private fun openUrl(url: String) {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
}
private fun setUpView() {
appGithub.setOnClickListener(this)
faqLink.setOnClickListener(this)
telegramLink.setOnClickListener(this)
appRate.setOnClickListener(this)
appTranslation.setOnClickListener(this)
appShare.setOnClickListener(this)
donateLink.setOnClickListener(this)
instagramLink.setOnClickListener(this)
twitterLink.setOnClickListener(this)
changelog.setOnClickListener(this)
openSource.setOnClickListener(this)
pinterestLink.setOnClickListener(this)
}
override fun onClick(view: View) {
when (view.id) {
R.id.pinterestLink -> openUrl(PINTEREST)
R.id.faqLink -> openUrl(FAQ_LINK)
R.id.telegramLink -> openUrl(APP_TELEGRAM_LINK)
R.id.discordLink -> openUrl(DISCORD_LINK)
R.id.appGithub -> openUrl(GITHUB_PROJECT)
R.id.appTranslation -> openUrl(TRANSLATE)
R.id.appRate -> openUrl(RATE_ON_GOOGLE_PLAY)
R.id.appShare -> shareApp()
R.id.donateLink -> NavigationUtil.goToSupportDevelopment(this)
R.id.instagramLink -> openUrl(APP_INSTAGRAM_LINK)
R.id.twitterLink -> openUrl(APP_TWITTER_LINK)
R.id.changelog -> showChangeLogOptions()
R.id.openSource -> NavigationUtil.goToOpenSource(this)
}
}
private fun showChangeLogOptions() {
MaterialDialog(this).show {
listItems(items = listOf("Telegram Channel", "App")) { _, position, _ ->
if (position == 0) {
openUrl(TELEGRAM_CHANGE_LOG)
} else {
NavigationUtil.gotoWhatNews(this@AboutActivity)
}
}
}
}
private fun getAppVersion(): String {
return try {
val packageInfo = packageManager.getPackageInfo(packageName, 0)
packageInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
"0.0.0"
}
}
private fun shareApp() {
val shareIntent = ShareCompat.IntentBuilder.from(this)
.setType("songText/plain")
.setText(String.format(getString(R.string.app_share), packageName))
.intent
if (shareIntent.resolveActivity(packageManager) != null) {
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.action_share)))
}
}
private fun loadContributors() {
val data = assetJsonData
val type = object : TypeToken<List<Contributor>>() {
}.type
val contributors = Gson().fromJson<List<Contributor>>(data, type)
val contributorAdapter = ContributorAdapter(contributors)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.adapter = contributorAdapter
}
}

View file

@ -0,0 +1,356 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.transition.Slide
import android.view.*
import android.view.animation.AnimationUtils
import android.widget.ImageView
import androidx.core.app.ActivityCompat
import androidx.core.util.Pair
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder.AlbumSongSortOrder
import code.name.monkey.retromusic.loaders.ArtistLoader
import code.name.monkey.retromusic.misc.AppBarStateChangeListener
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.mvp.contract.AlbumDetailsContract
import code.name.monkey.retromusic.mvp.presenter.AlbumDetailsPresenter
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
import code.name.monkey.retromusic.activities.tageditor.AlbumTagEditorActivity
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.appbar.AppBarLayout
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_album.*
import kotlinx.android.synthetic.main.activity_album_content.*
import java.util.*
class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsContract.AlbumDetailsView {
private lateinit var albumDetailsPresenter: AlbumDetailsPresenter
private lateinit var simpleSongAdapter: SimpleSongAdapter
private var disposable = CompositeDisposable()
var album: Album? = null
private set
private val savedSortOrder: String
get() = PreferenceUtil.getInstance().albumDetailSongSortOrder
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_album)
}
private fun setupWindowTransition() {
val slide = Slide(Gravity.BOTTOM)
slide.interpolator = AnimationUtils.loadInterpolator(this, android.R.interpolator.linear_out_slow_in)
window.enterTransition = slide
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
setupWindowTransition()
super.onCreate(savedInstanceState)
toggleBottomNavigationView(true)
collapsingToolbarLayout?.setBackgroundColor(ThemeStore.primaryColor(this))
setLightNavigationBar(true)
setNavigationbarColorAuto()
ActivityCompat.postponeEnterTransition(this)
val albumId = intent.getIntExtra(EXTRA_ALBUM_ID, -1)
albumDetailsPresenter = AlbumDetailsPresenter(this, albumId)
albumDetailsPresenter.subscribe()
setupRecyclerView()
setupToolbarMarginHeight()
contentContainer.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
run {
if (scrollY > oldScrollY) {
actionShuffleAll.shrink(true)
}
if (scrollY < oldScrollY) {
actionShuffleAll.extend(true)
}
}
}
actionShuffleAll.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(album!!.songs!!, true) }
artistImage = findViewById(R.id.artistImage)
artistImage.setOnClickListener {
val artistPairs = arrayOf<Pair<*, *>>(Pair.create(image, resources.getString(R.string.transition_artist_image)))
NavigationUtil.goToArtist(this, album!!.artistId, *artistPairs)
}
}
private fun setupRecyclerView() {
simpleSongAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song, false)
recyclerView.apply {
layoutManager = LinearLayoutManager(this@AlbumDetailsActivity)
itemAnimator = DefaultItemAnimator()
isNestedScrollingEnabled = false
adapter = simpleSongAdapter
}
}
private fun setupToolbarMarginHeight() {
setSupportActionBar(toolbar)
supportActionBar!!.title = null
val primaryColor = ThemeStore.primaryColor(this)
TintHelper.setTintAuto(contentContainer!!, primaryColor, true)
if (collapsingToolbarLayout != null) {
collapsingToolbarLayout!!.apply {
setContentScrimColor(primaryColor)
setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor))
}
}
toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
if (toolbar != null && !PreferenceUtil.getInstance().fullScreenMode) {
val params = toolbar!!.layoutParams as ViewGroup.MarginLayoutParams
params.topMargin = RetroUtil.getStatusBarHeight()
toolbar!!.layoutParams = params
}
appBarLayout?.apply {
addOnOffsetChangedListener(object : AppBarStateChangeListener() {
override fun onStateChanged(appBarLayout: AppBarLayout, state: AppBarStateChangeListener.State) {
val color: Int = when (state) {
AppBarStateChangeListener.State.COLLAPSED -> {
setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(this@AlbumDetailsActivity)))
ThemeStore.primaryColor(this@AlbumDetailsActivity)
}
AppBarStateChangeListener.State.EXPANDED, AppBarStateChangeListener.State.IDLE -> {
setLightStatusbar(false)
Color.TRANSPARENT
}
}
ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(this@AlbumDetailsActivity, toolbar, color)
}
})
}
}
override fun onPause() {
super.onPause()
albumDetailsPresenter.unsubscribe()
}
override fun onDestroy() {
super.onDestroy()
disposable.dispose()
}
override fun loading() {
}
override fun showEmptyView() {
}
override fun completed() {
ActivityCompat.startPostponedEnterTransition(this)
}
override fun showData(list: Album) {
if (list.songs!!.isEmpty()) {
finish()
return
}
this.album = list
albumTitle.text = list.title
albumText.text = String.format("%s • %s • %s", list.artistName, MusicUtil.getYearString(list.year), MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, list.songs)))
loadAlbumCover()
loadMoreFrom(list)
simpleSongAdapter.swapDataSet(list.songs)
}
private lateinit var artistImage: ImageView
private fun loadMoreFrom(album: Album) {
disposable.add(ArtistLoader.getArtist(this, album.artistId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map {
GlideApp.with(this@AlbumDetailsActivity)
.asBitmapPalette()
.load(RetroGlideExtension.getArtistModel(it))
.transition(RetroGlideExtension.getDefaultTransition())
.artistOptions(it)
.dontAnimate()
.into(object : RetroMusicColoredTarget(artistImage) {
override fun onColorReady(color: Int) {
}
})
return@map it.albums!!
}
.map { it.filter { albumSearch -> albumSearch.id != album.id } }
.subscribe {
for (albumFinal in it) {
if (albumFinal.id == album.id)
println("$albumFinal -> $album")
}
if (it.isEmpty()) {
return@subscribe
}
moreTitle.visibility = View.VISIBLE
moreRecyclerView.visibility = View.VISIBLE
moreTitle.text = String.format("More from %s", album.artistName)
val albumAdapter = HorizontalAlbumAdapter(this, it as ArrayList<Album>, false, null)
moreRecyclerView.layoutManager = GridLayoutManager(this, 1, GridLayoutManager.HORIZONTAL, false)
moreRecyclerView.adapter = albumAdapter
})
}
private fun loadAlbumCover() {
GlideApp.with(this)
.asBitmapPalette()
.load(RetroGlideExtension.getSongModel(album!!.safeGetFirstSong()))
.transition(RetroGlideExtension.getDefaultTransition())
.songOptions(album!!.safeGetFirstSong())
.dontAnimate()
.into(object : RetroMusicColoredTarget(image as ImageView) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
}
private fun setColors(color: Int) {
val themeColor = if (PreferenceUtil.getInstance().adaptiveColor) color else ThemeStore.accentColor(this)
songTitle.setTextColor(themeColor)
moreTitle.setTextColor(themeColor)
actionShuffleAll.backgroundTintList = ColorStateList.valueOf(themeColor)
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(themeColor))).apply {
actionShuffleAll.setTextColor(this)
actionShuffleAll.iconTint = this
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_album_detail, menu)
val sortOrder = menu.findItem(R.id.action_sort_order)
setUpSortOrderMenu(sortOrder.subMenu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
var sortOrder: String? = null
val songs = simpleSongAdapter.dataSet
when (item.itemId) {
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs).show(supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_delete_from_device -> {
DeleteSongsDialog.create(songs).show(supportFragmentManager, "DELETE_SONGS")
return true
}
android.R.id.home -> {
super.onBackPressed()
return true
}
R.id.action_tag_editor -> {
val intent = Intent(this, AlbumTagEditorActivity::class.java)
intent.putExtra(AbsTagEditorActivity.EXTRA_ID, album!!.id)
startActivityForResult(intent, TAG_EDITOR_REQUEST)
return true
}
R.id.action_go_to_artist -> {
NavigationUtil.goToArtist(this, album!!.artistId)
return true
}
/*Sort*/
R.id.action_sort_order_title -> sortOrder = AlbumSongSortOrder.SONG_A_Z
R.id.action_sort_order_title_desc -> sortOrder = AlbumSongSortOrder.SONG_Z_A
R.id.action_sort_order_track_list -> sortOrder = AlbumSongSortOrder.SONG_TRACK_LIST
R.id.action_sort_order_artist_song_duration -> sortOrder = AlbumSongSortOrder.SONG_DURATION
}
if (sortOrder != null) {
item.isChecked = true
setSaveSortOrder(sortOrder)
}
return true
}
private fun setUpSortOrderMenu(sortOrder: SubMenu) {
when (savedSortOrder) {
AlbumSongSortOrder.SONG_A_Z -> sortOrder.findItem(R.id.action_sort_order_title).isChecked = true
AlbumSongSortOrder.SONG_Z_A -> sortOrder.findItem(R.id.action_sort_order_title_desc).isChecked = true
AlbumSongSortOrder.SONG_TRACK_LIST -> sortOrder.findItem(R.id.action_sort_order_track_list).isChecked = true
AlbumSongSortOrder.SONG_DURATION -> sortOrder.findItem(R.id.action_sort_order_artist_song_duration).isChecked = true
}
}
private fun setSaveSortOrder(sortOrder: String?) {
PreferenceUtil.getInstance().albumDetailSongSortOrder = sortOrder
reload()
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
reload()
}
private fun reload() {
albumDetailsPresenter.subscribe()
}
companion object {
const val EXTRA_ALBUM_ID = "extra_album_id"
private const val TAG_EDITOR_REQUEST = 2001
}
}

View file

@ -0,0 +1,368 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.text.Spanned
import android.transition.Slide
import android.view.*
import android.view.animation.AnimationUtils
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.misc.AppBarStateChangeListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.contract.ArtistDetailContract
import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsPresenter
import code.name.monkey.retromusic.rest.LastFMRestClient
import code.name.monkey.retromusic.rest.model.LastFmArtist
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.util.*
import com.google.android.material.appbar.AppBarLayout
import kotlinx.android.synthetic.main.activity_artist_content.*
import kotlinx.android.synthetic.main.activity_artist_details.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.*
import kotlin.collections.ArrayList
class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailContract.ArtistsDetailsView {
private var biography: Spanned? = null
private lateinit var artist: Artist
private var lastFMRestClient: LastFMRestClient? = null
private var artistDetailsPresenter: ArtistDetailsPresenter? = null
private lateinit var songAdapter: SimpleSongAdapter
private lateinit var albumAdapter: AlbumAdapter
private var forceDownload: Boolean = false
private fun setupWindowTransitions() {
val slide = Slide(Gravity.BOTTOM)
slide.interpolator = AnimationUtils.loadInterpolator(this, android.R.interpolator.linear_out_slow_in)
window.enterTransition = slide
}
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_artist_details)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
setupWindowTransitions()
super.onCreate(savedInstanceState)
collapsingToolbarLayout?.setBackgroundColor(ThemeStore.primaryColor(this))
toggleBottomNavigationView(true)
setNavigationbarColorAuto()
setLightNavigationBar(true)
ActivityCompat.postponeEnterTransition(this)
lastFMRestClient = LastFMRestClient(this)
setUpViews()
artistDetailsPresenter = ArtistDetailsPresenter(this, intent.extras!!)
artistDetailsPresenter!!.subscribe()
contentContainer.setOnScrollChangeListener { _: NestedScrollView?, _: Int, scrollY: Int, _: Int, oldScrollY: Int ->
run {
if (scrollY > oldScrollY) {
actionShuffleAll.shrink(true)
}
if (scrollY < oldScrollY) {
actionShuffleAll.extend(true)
}
}
}
biographyText.setOnClickListener {
if (biographyText.maxLines == 4) {
biographyText.maxLines = Integer.MAX_VALUE
} else {
biographyText.maxLines = 4
}
}
actionShuffleAll.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(getArtist().songs, true) }
}
private fun setUpViews() {
setupRecyclerView()
setupToolbarMarginHeight()
setupContainerHeight()
}
private fun setupContainerHeight() {
if (imageContainer != null) {
val params = imageContainer!!.layoutParams
params.width = DensityUtil.getScreenHeight(this) / 2
imageContainer!!.layoutParams = params
}
}
private fun setupToolbarMarginHeight() {
val primaryColor = ThemeStore.primaryColor(this)
TintHelper.setTintAuto(contentContainer!!, primaryColor, true)
if (collapsingToolbarLayout != null) {
collapsingToolbarLayout!!.setContentScrimColor(primaryColor)
collapsingToolbarLayout!!.setStatusBarScrimColor(ColorUtil.darkenColor(primaryColor))
}
toolbar!!.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
setSupportActionBar(toolbar)
supportActionBar!!.title = null
if (toolbar != null && !PreferenceUtil.getInstance().fullScreenMode) {
val params = toolbar!!.layoutParams as ViewGroup.MarginLayoutParams
params.topMargin = RetroUtil.getStatusBarHeight()
toolbar!!.layoutParams = params
}
appBarLayout?.addOnOffsetChangedListener(object : AppBarStateChangeListener() {
override fun onStateChanged(appBarLayout: AppBarLayout, state: AppBarStateChangeListener.State) {
val color: Int = when (state) {
AppBarStateChangeListener.State.COLLAPSED -> {
setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(appBarLayout.context)))
ThemeStore.primaryColor(appBarLayout.context)
}
AppBarStateChangeListener.State.EXPANDED, AppBarStateChangeListener.State.IDLE -> {
setLightStatusbar(false)
Color.TRANSPARENT
}
}
ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(appBarLayout.context, toolbar, color)
}
})
}
private fun setupRecyclerView() {
albumAdapter = HorizontalAlbumAdapter(this, ArrayList(), false, null)
albumRecyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false)
adapter = albumAdapter
}
songAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song, false)
recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this.context)
adapter = songAdapter
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
CustomArtistImageUtil.getInstance(this).setCustomArtistImage(artist!!, data!!.data!!)
}
else -> if (resultCode == Activity.RESULT_OK) {
reload()
}
}
}
override fun onPause() {
super.onPause()
artistDetailsPresenter!!.unsubscribe()
}
override fun loading() {}
override fun showEmptyView() {
}
override fun completed() {
ActivityCompat.startPostponedEnterTransition(this)
}
override fun showData(list: Artist) {
setArtist(list)
}
private fun getArtist(): Artist {
if (artist == null) {
artist = Artist()
}
return this.artist!!
}
private fun setArtist(artist: Artist) {
if (artist.songCount <= 0) {
finish()
}
this.artist = artist
loadArtistImage()
if (RetroUtil.isAllowedToDownloadMetadata(this)) {
loadBiography()
}
artistTitle.text = artist.name
text.text = String.format("%s • %s", MusicUtil.getArtistInfoString(this, artist), MusicUtil
.getReadableDurationString(MusicUtil.getTotalDuration(this, artist.songs)))
//val songs = artist.songs.sortedWith(compareBy { it.title }) as ArrayList<Song>
songAdapter.swapDataSet(artist.songs)
//val albums = artist.albums?.sortedWith(compareBy { it.artistName }) as ArrayList<Album>
albumAdapter.swapDataSet(artist.albums!!)
}
private fun loadBiography(lang: String? = Locale.getDefault().language) {
biography = null
lastFMRestClient!!.apiService
.getArtistInfo(getArtist().name, lang, null)
.enqueue(object : Callback<LastFmArtist> {
override fun onResponse(call: Call<LastFmArtist>,
response: Response<LastFmArtist>) {
val lastFmArtist = response.body()
if (lastFmArtist != null && lastFmArtist.artist != null) {
val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && !bioContent.trim { it <= ' ' }.isEmpty()) {
//TransitionManager.beginDelayedTransition(titleContainer);
biographyText.visibility = View.VISIBLE
biographyTitle.visibility = View.VISIBLE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
biography = Html.fromHtml(bioContent, Html.FROM_HTML_MODE_LEGACY)
} else {
biography = Html.fromHtml(bioContent)
}
biographyText!!.text = biography
}
}
// If the "lang" parameter is set and no biography is given, retry with default language
if (biography == null && lang != null) {
loadBiography(null)
}
}
override fun onFailure(call: Call<LastFmArtist>, t: Throwable) {
t.printStackTrace()
biography = null
}
})
}
private fun loadArtistImage() {
GlideApp.with(this)
.asBitmapPalette()
.load(RetroGlideExtension.getArtistModel(artist, forceDownload))
.transition(RetroGlideExtension.getDefaultTransition())
.artistOptions(artist)
.dontAnimate()
.into(object : RetroMusicColoredTarget(artistImage) {
override fun onColorReady(color: Int) {
setColors(color)
}
})
forceDownload = false;
}
private fun setColors(color: Int) {
val textColor = if (PreferenceUtil.getInstance().adaptiveColor) color else ThemeStore.accentColor(this)
albumTitle.setTextColor(textColor)
songTitle.setTextColor(textColor)
biographyTitle.setTextColor(textColor)
actionShuffleAll.backgroundTintList = ColorStateList.valueOf(textColor)
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(textColor))).apply {
actionShuffleAll.setTextColor(this)
actionShuffleAll.iconTint = this
}
findViewById<View>(R.id.root).setBackgroundColor(ThemeStore.primaryColor(this))
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
val songs = getArtist().songs
when (item.itemId) {
android.R.id.home -> {
super.onBackPressed()
return true
}
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs).show(supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_set_artist_image -> {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), REQUEST_CODE_SELECT_IMAGE)
return true
}
R.id.action_reset_artist_image -> {
Toast.makeText(this@ArtistDetailActivity, resources.getString(R.string.updating),
Toast.LENGTH_SHORT).show()
CustomArtistImageUtil.getInstance(this@ArtistDetailActivity).resetCustomArtistImage(artist!!)
forceDownload = true
return true
}
}
return true
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_artist_detail, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
reload()
}
private fun reload() {
artistDetailsPresenter!!.unsubscribe()
artistDetailsPresenter!!.subscribe()
}
companion object {
const val EXTRA_ARTIST_ID = "extra_artist_id"
const val REQUEST_CODE_SELECT_IMAGE = 9003
}
}

View file

@ -0,0 +1,190 @@
package code.name.monkey.retromusic.activities
import android.os.Bundle
import android.transition.TransitionManager
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.SeekBar
import android.widget.TextView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.EqualizerHelper
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.util.ViewUtil
import kotlinx.android.synthetic.main.activity_equalizer.*
/**
* @author Hemanth S (h4h13).
*/
class EqualizerActivity : AbsMusicServiceActivity(), AdapterView.OnItemSelectedListener {
private val seekBarChangeListener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
if (seekBar === bassBoostStrength) {
bassBoost.isEnabled = progress > 0
EqualizerHelper.instance!!.bassBoostStrength = progress
EqualizerHelper.instance!!.isBassBoostEnabled = progress > 0
} else if (seekBar === virtualizerStrength) {
virtualizer.isEnabled = progress > 0
EqualizerHelper.instance!!.isVirtualizerEnabled = progress > 0
EqualizerHelper.instance!!.virtualizerStrength = progress
}
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
}
}
private var presetsNamesAdapter: ArrayAdapter<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_equalizer)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
equalizerSwitch.isChecked = EqualizerHelper.instance!!.equalizer.enabled
equalizerSwitch.setBackgroundColor(ThemeStore.accentColor(this))
val widgetColor = MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))
equalizerSwitch.setTextColor(widgetColor)
TintHelper.setTintAuto(equalizerSwitch, widgetColor, false)
equalizerSwitch.setOnCheckedChangeListener { buttonView, isChecked ->
when (buttonView.id) {
R.id.equalizerSwitch -> {
EqualizerHelper.instance!!.equalizer.enabled = isChecked
TransitionManager.beginDelayedTransition(content)
content.visibility = if (isChecked) View.VISIBLE else View.GONE
}
}
}
presetsNamesAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1)
presets.adapter = presetsNamesAdapter
presets.onItemSelectedListener = this
bassBoostStrength.progress = EqualizerHelper.instance!!.bassBoostStrength
ViewUtil.setProgressDrawable(bassBoostStrength, ThemeStore.accentColor(this))
bassBoostStrength.setOnSeekBarChangeListener(seekBarChangeListener)
virtualizerStrength.progress = EqualizerHelper.instance!!.virtualizerStrength
ViewUtil.setProgressDrawable(virtualizerStrength, ThemeStore.accentColor(this))
virtualizerStrength.setOnSeekBarChangeListener(seekBarChangeListener)
setupUI()
addPresets()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun setupToolbar() {
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
val primaryColor = ThemeStore.primaryColor(this)
appBarLayout.setBackgroundColor(primaryColor)
toolbar.apply {
setBackgroundColor(primaryColor)
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
setNavigationOnClickListener { onBackPressed() }
setSupportActionBar(this)
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.textColorSecondary(context))
}
title = null
}
private fun addPresets() {
presetsNamesAdapter!!.clear()
presetsNamesAdapter!!.add("Custom")
for (j in 0 until EqualizerHelper.instance!!.equalizer.numberOfPresets) {
presetsNamesAdapter!!
.add(EqualizerHelper.instance!!.equalizer.getPresetName(j.toShort()))
presetsNamesAdapter!!.notifyDataSetChanged()
}
presets.setSelection(EqualizerHelper.instance!!.equalizer.currentPreset.toInt() + 1)
}
private fun setupUI() {
frequencyBands.removeAllViews()
val bands: Short
try {
// get number of supported bands
bands = EqualizerHelper.instance!!.numberOfBands.toShort()
// for each of the supported bands, we will set up a slider from -10dB to 10dB boost/attenuation,
// as well as text labels to assist the user
for (i in 0 until bands) {
val view = LayoutInflater.from(this).inflate(R.layout.retro_seekbar, frequencyBands, false)
val freqTextView = view.findViewById<TextView>(R.id.hurtz)
freqTextView.text = String.format("%d Hz", EqualizerHelper.instance!!.getCenterFreq(i) / 1000)
val minDbTextView = view.findViewById<TextView>(R.id.minus_db)
minDbTextView.text = String.format("%d dB", EqualizerHelper.instance!!.bandLevelLow / 100)
val maxDbTextView = view.findViewById<TextView>(R.id.plus_db)
maxDbTextView.text = String.format("%d dB", EqualizerHelper.instance!!.bandLevelHigh / 100)
val bar = view.findViewById<SeekBar>(R.id.seekbar)
ViewUtil.setProgressDrawable(bar, ThemeStore.accentColor(this))
bar.max = EqualizerHelper.instance!!.bandLevelHigh - EqualizerHelper.instance!!
.bandLevelLow
bar.progress = EqualizerHelper.instance!!.getBandLevel(i) - EqualizerHelper.instance!!
.bandLevelLow
bar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
EqualizerHelper.instance!!.setBandLevel(i,
progress + EqualizerHelper.instance!!.bandLevelLow)
if (fromUser) {
presets.setSelection(0)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
frequencyBands.addView(view)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
if (position == 0) {
return
}
EqualizerHelper.instance!!.equalizer.usePreset((position - 1).toShort())
setupUI()
}
override fun onNothingSelected(parent: AdapterView<*>) {
}
}

View file

@ -0,0 +1,182 @@
package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.GenreMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.contract.GenreDetailsContract
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.ViewUtil
import com.afollestad.materialcab.MaterialCab
import kotlinx.android.synthetic.main.activity_playlist_detail.*
import java.util.*
/**
* @author Hemanth S (h4h13).
*/
class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), GenreDetailsContract.GenreDetailsView, CabHolder {
private var genre: Genre? = null
private var presenter: GenreDetailsPresenter? = null
private var songAdapter: SongAdapter? = null
private var cab: MaterialCab? = null
private fun checkIsEmpty() {
empty!!.visibility = if (songAdapter!!.itemCount == 0) View.VISIBLE else View.GONE
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColor(Color.TRANSPARENT)
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(this)))
toggleBottomNavigationView(true)
genre = intent.extras!!.getParcelable(EXTRA_GENRE_ID)
presenter = GenreDetailsPresenter(this, genre!!.id)
setUpToolBar()
setupRecyclerView()
actionShuffleAll.setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(songAdapter!!.dataSet, true) }
}
private fun setUpToolBar() {
bannerTitle!!.text = genre!!.name
bannerTitle!!.setTextColor(ThemeStore.textColorPrimary(this))
val primaryColor = ThemeStore.primaryColor(this)
appBarLayout.setBackgroundColor(primaryColor)
toolbar.apply {
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
setBackgroundColor(primaryColor)
setSupportActionBar(this)
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.textColorSecondary(this@GenreDetailsActivity))
}
actionShuffleAll.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply {
actionShuffleAll.setTextColor(this)
actionShuffleAll.iconTint = this
}
title = null
}
override fun onResume() {
super.onResume()
presenter!!.subscribe()
}
override fun onPause() {
super.onPause()
presenter!!.unsubscribe()
}
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail)
}
override fun loading() {
}
override fun showEmptyView() {
}
override fun completed() {
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_genre_detail, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return GenreMenuHelper.handleMenuClick(this, genre!!, item)
}
private fun setupRecyclerView() {
ViewUtil.setUpFastScrollRecyclerViewColor(this, recyclerView, ThemeStore.accentColor(this))
songAdapter = SongAdapter(this, ArrayList(), R.layout.item_list, false, this)
recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this@GenreDetailsActivity)
adapter = songAdapter
}.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
actionShuffleAll.shrink(true)
} else if (dy < 0) {
actionShuffleAll.extend(true)
}
}
})
songAdapter!!.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkIsEmpty()
}
})
}
override fun showData(list: ArrayList<Song>) {
songAdapter!!.swapDataSet(list)
}
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
if (cab != null && cab!!.isActive) cab!!.finish()
cab = MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this)))
.start(callback)
return cab!!
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive)
cab!!.finish()
else {
recyclerView!!.stopScroll()
super.onBackPressed()
}
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
presenter!!.subscribe()
}
companion object {
const val EXTRA_GENRE_ID = "extra_genre_id"
}
}

View file

@ -0,0 +1,44 @@
package code.name.monkey.retromusic.activities
import android.os.Bundle
import android.view.MenuItem
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import kotlinx.android.synthetic.main.activity_license.*
class LicenseActivity : AbsBaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_license)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
license.loadUrl("file:///android_asset/index.html")
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
toolbar!!.apply {
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
setNavigationOnClickListener { onBackPressed() }
setBackgroundColor(ThemeStore.primaryColor(this@LicenseActivity))
}
appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this))
title = null
setSupportActionBar(toolbar)
ToolbarContentTintHelper.colorBackButton(toolbar!!, ThemeStore.accentColor(this))
}
}

View file

@ -0,0 +1,111 @@
package code.name.monkey.retromusic.activities
import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.core.view.ViewCompat
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenPlayerControlsFragment
import com.r0adkll.slidr.Slidr
import com.r0adkll.slidr.model.SlidrConfig
import com.r0adkll.slidr.model.SlidrListener
import com.r0adkll.slidr.model.SlidrPosition
import kotlinx.android.synthetic.main.activity_album.*
class LockScreenActivity : AbsMusicServiceActivity() {
private var fragment: LockScreenPlayerControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
} else {
this.window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
}
setDrawUnderStatusBar()
setContentView(R.layout.activity_lock_screen_old_style)
hideStatusBar()
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
val config = SlidrConfig.Builder()
.listener(object : SlidrListener {
override fun onSlideStateChanged(state: Int) {
}
override fun onSlideChange(percent: Float) {
}
override fun onSlideOpened() {
}
override fun onSlideClosed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null)
}
}
})
.position(SlidrPosition.BOTTOM)
.build()
Slidr.attach(this, config)
fragment = supportFragmentManager.findFragmentById(R.id.playback_controls_fragment) as LockScreenPlayerControlsFragment?
findViewById<View>(R.id.slide).apply {
translationY = 100f
alpha = 0f
ViewCompat.animate(this)
.translationY(0f)
.alpha(1f)
.setDuration(1500)
.start()
}
findViewById<View>(R.id.root_layout).setBackgroundColor(ThemeStore.primaryColor(this))
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
updateSongs()
}
override fun onServiceConnected() {
super.onServiceConnected()
updateSongs()
}
private fun updateSongs() {
val song = MusicPlayerRemote.currentSong
GlideApp.with(this)
.asBitmapPalette()
.load(RetroGlideExtension.getSongModel(song))
.transition(RetroGlideExtension.getDefaultTransition())
.songOptions(song)
.dontAnimate()
.into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(color: Int) {
fragment!!.setDark(color)
}
})
}
}

View file

@ -0,0 +1,374 @@
package code.name.monkey.retromusic.activities
import android.annotation.SuppressLint
import android.content.res.ColorStateList
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.text.InputType
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 code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.lyrics.LrcHelper
import code.name.monkey.retromusic.lyrics.LrcView
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.activities.tageditor.WriteTagsAsyncTask
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import kotlinx.android.synthetic.main.activity_lyrics.*
import kotlinx.android.synthetic.main.fragment_lyrics.*
import kotlinx.android.synthetic.main.fragment_synced.*
import org.jaudiotagger.tag.FieldKey
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
class LyricsActivity : AbsMusicServiceActivity(), View.OnClickListener, ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
when (state) {
ViewPager.SCROLL_STATE_IDLE ->
fab.show(true)
ViewPager.SCROLL_STATE_DRAGGING,
ViewPager.SCROLL_STATE_SETTLING ->
fab.hide(true)
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
PreferenceUtil.getInstance().lyricsOptions = position
if (position == 0) fab.text = 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() {
var baseUrl = "http://www.google.com/search?"
var query = song.title + "+" + song.artistName
query = "q=" + query.replace(" ", "+") + " .lrc"
baseUrl += query
return baseUrl
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lyrics)
setStatusbarColorAuto()
setTaskDescriptionColorAuto()
setNavigationbarColorAuto()
appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this))
toolbar.apply {
setBackgroundColor(ThemeStore.primaryColor(this@LyricsActivity))
navigationIcon = TintHelper.createTintedDrawable(ContextCompat.getDrawable(this@LyricsActivity, R.drawable.ic_keyboard_backspace_black_24dp), ThemeStore.textColorSecondary(this@LyricsActivity))
setSupportActionBar(toolbar)
}
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.getInstance().lyricsOptions
addOnPageChangeListener(this@LyricsActivity)
}
tabs.apply {
setupWithViewPager(viewPager)
setSelectedTabIndicator(TintHelper.createTintedDrawable(ContextCompat.getDrawable(this@LyricsActivity, R.drawable.tab_indicator), ThemeStore.accentColor(this@LyricsActivity)))
setTabTextColors(ThemeStore.textColorSecondary(this@LyricsActivity), ThemeStore.accentColor(this@LyricsActivity))
setSelectedTabIndicatorColor(ThemeStore.accentColor(context))
}
fab.setOnClickListener(this)
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
song = MusicPlayerRemote.currentSong
toolbar.title = song.title
toolbar.subtitle = song.artistName
}
override fun onServiceConnected() {
super.onServiceConnected()
song = MusicPlayerRemote.currentSong
toolbar.title = song.title
toolbar.subtitle = song.artistName
}
private fun setupWakelock() {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
private fun showSyncedLyrics() {
var content = ""
try {
content = LyricUtil.getStringFromFile(song.title, song.artistName)
} catch (e: Exception) {
e.printStackTrace()
}
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()
}
}
}
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!!
}
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()
}
}
}
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) {
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.context.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 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 {
val context = activity!!
setCurrentPlayLineColor(ThemeStore.accentColor(context))
setIndicatorTextColor(ThemeStore.accentColor(context))
setCurrentIndicateLineTextColor(ThemeStore.textColorPrimary(context))
setNoLrcTextColor(ThemeStore.textColorPrimary(context))
setOnPlayIndicatorLineListener(object : LrcView.OnPlayIndicatorLineListener {
override fun onPlay(time: Long, content: String) {
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() {
val song = MusicPlayerRemote.currentSong
if (LyricUtil.isLrcFileExist(song.title, song.artistName)) {
showLyricsLocal(LyricUtil.getLocalLyricFile(song.title, song.artistName))
}
}
private fun showLyricsLocal(file: File?) {
if (file != null) {
lyricsView.setLrcData(LrcHelper.parseLrcFromFile(file))
}
}
}
}

View file

@ -0,0 +1,307 @@
package code.name.monkey.retromusic.activities
import android.annotation.SuppressLint
import android.content.*
import android.content.pm.PackageManager
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SearchQueryHelper
import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks
import code.name.monkey.retromusic.loaders.AlbumLoader
import code.name.monkey.retromusic.loaders.ArtistLoader
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.fragments.mainactivity.LibraryFragment
import code.name.monkey.retromusic.fragments.mainactivity.home.BannerHomeFragment
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import io.reactivex.disposables.CompositeDisposable
import java.util.*
class MainActivity : AbsSlidingMusicPanelActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var currentFragment: MainActivityFragmentCallbacks
private var blockRequestPermissions: Boolean = false
private val disposable = CompositeDisposable()
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action != null && action == Intent.ACTION_SCREEN_OFF) {
if (PreferenceUtil.getInstance().lockScreen && MusicPlayerRemote.isPlaying) {
val activity = Intent(context, LockScreenActivity::class.java)
activity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activity.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
ActivityCompat.startActivity(context, activity, null)
}
}
}
}
override fun createContentView(): View {
@SuppressLint("InflateParams")
val contentView = layoutInflater.inflate(R.layout.activity_main_drawer_layout, null)
val drawerContent = contentView.findViewById<ViewGroup>(R.id.drawer_content_container)
drawerContent.addView(wrapSlidingMusicPanel(R.layout.activity_main_content))
return contentView
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
getBottomNavigationView().setOnNavigationItemSelectedListener {
PreferenceUtil.getInstance().lastPage = it.itemId
selectedFragment(it.itemId)
true
}
//setUpDrawerLayout()
if (savedInstanceState == null) {
selectedFragment(PreferenceUtil.getInstance().lastPage);
} else {
restoreCurrentFragment();
}
checkShowChangelog()
/*if (!App.isProVersion && !PreferenceManager.getDefaultSharedPreferences(this).getBoolean("shown", false)) {
showPromotionalOffer()
}*/
}
private fun checkShowChangelog() {
try {
val pInfo = packageManager.getPackageInfo(packageName, 0)
val currentVersion = pInfo.versionCode
if (currentVersion != PreferenceUtil.getInstance().lastChangelogVersion) {
startActivityForResult(Intent(this, WhatsNewActivity::class.java), APP_INTRO_REQUEST)
}
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
}
override fun onResume() {
super.onResume()
val screenOnOff = IntentFilter()
screenOnOff.addAction(Intent.ACTION_SCREEN_OFF)
registerReceiver(broadcastReceiver, screenOnOff)
PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this)
if (intent.hasExtra("expand")) {
if (intent.getBooleanExtra("expand", false)) {
expandPanel()
intent.putExtra("expand", false)
}
}
}
override fun onDestroy() {
super.onDestroy()
disposable.clear()
unregisterReceiver(broadcastReceiver)
PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this)
}
fun setCurrentFragment(fragment: Fragment, b: Boolean) {
val trans = supportFragmentManager.beginTransaction()
trans.replace(R.id.fragment_container, fragment, null)
if (b) {
trans.addToBackStack(null)
}
trans.commit()
currentFragment = fragment as MainActivityFragmentCallbacks
}
private fun restoreCurrentFragment() {
currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as MainActivityFragmentCallbacks
}
private fun handlePlaybackIntent(intent: Intent?) {
if (intent == null) {
return
}
val uri = intent.data
val mimeType = intent.type
var handled = false
if (intent.action != null && intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH) {
val songs = SearchQueryHelper.getSongs(this, intent.extras!!)
if (MusicPlayerRemote.shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
MusicPlayerRemote.openAndShuffleQueue(songs, true)
} else {
MusicPlayerRemote.openQueue(songs, 0, true)
}
handled = true
}
if (uri != null && uri.toString().isNotEmpty()) {
MusicPlayerRemote.playFromUri(uri)
handled = true
} else if (MediaStore.Audio.Playlists.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "playlistId", "playlist").toInt()
if (id >= 0) {
val position = intent.getIntExtra("position", 0)
val songs = ArrayList(PlaylistSongsLoader.getPlaylistSongList(this, id).blockingFirst())
MusicPlayerRemote.openQueue(songs, position, true)
handled = true
}
} else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "albumId", "album").toInt()
if (id >= 0) {
val position = intent.getIntExtra("position", 0)
MusicPlayerRemote.openQueue(AlbumLoader.getAlbum(this, id).blockingFirst().songs!!, position, true)
handled = true
}
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "artistId", "artist").toInt()
if (id >= 0) {
val position = intent.getIntExtra("position", 0)
MusicPlayerRemote.openQueue(ArtistLoader.getArtist(this, id).blockingFirst().songs, position, true)
handled = true
}
}
if (handled) {
setIntent(Intent())
}
}
private fun parseIdFromIntent(intent: Intent, longKey: String, stringKey: String): Long {
var id = intent.getLongExtra(longKey, -1)
if (id < 0) {
val idString = intent.getStringExtra(stringKey)
if (idString != null) {
try {
id = java.lang.Long.parseLong(idString)
} catch (e: NumberFormatException) {
Log.e(TAG, e.message)
}
}
}
return id
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
APP_INTRO_REQUEST -> {
blockRequestPermissions = false
if (!hasPermissions()) {
requestPermissions()
}
}
REQUEST_CODE_THEME, APP_USER_INFO_REQUEST -> postRecreate()
PURCHASE_REQUEST -> {
if (resultCode == RESULT_OK) {
//checkSetUpPro();
}
}
}
}
override fun handleBackPress(): Boolean {
return super.handleBackPress() || currentFragment.handleBackPress()
}
override fun onServiceConnected() {
super.onServiceConnected()
handlePlaybackIntent(intent)
}
override fun requestPermissions() {
if (!blockRequestPermissions) {
super.requestPermissions()
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (key == PreferenceUtil.GENERAL_THEME ||
key == PreferenceUtil.ADAPTIVE_COLOR_APP ||
key == PreferenceUtil.DOMINANT_COLOR ||
key == PreferenceUtil.USER_NAME ||
key == PreferenceUtil.TOGGLE_FULL_SCREEN ||
key == PreferenceUtil.TOGGLE_VOLUME ||
key == PreferenceUtil.ROUND_CORNERS ||
key == PreferenceUtil.CAROUSEL_EFFECT ||
key == PreferenceUtil.NOW_PLAYING_SCREEN_ID ||
key == PreferenceUtil.TOGGLE_GENRE ||
key == PreferenceUtil.BANNER_IMAGE_PATH ||
key == PreferenceUtil.PROFILE_IMAGE_PATH ||
key == PreferenceUtil.CIRCULAR_ALBUM_ART ||
key == PreferenceUtil.KEEP_SCREEN_ON ||
key == PreferenceUtil.TOGGLE_SEPARATE_LINE ||
key == PreferenceUtil.ALBUM_GRID_STYLE ||
key == PreferenceUtil.ARTIST_GRID_STYLE ||
key == PreferenceUtil.TOGGLE_HOME_BANNER ||
key == PreferenceUtil.TOGGLE_ADD_CONTROLS ||
key == PreferenceUtil.ALBUM_COVER_STYLE ||
key == PreferenceUtil.HOME_ARTIST_GRID_STYLE ||
key == PreferenceUtil.ALBUM_COVER_TRANSFORM ||
key == PreferenceUtil.TAB_TEXT_MODE)
postRecreate()
}
private fun showPromotionalOffer() {
/*MaterialDialog(this).show {
positiveButton(text = "Buy") { startActivity(Intent(this@MainActivity, PurchaseActivity::class.java)) }
negativeButton(android.R.string.cancel)
customView(R.layout.dialog_promotional_offer)
onDismiss {
PreferenceManager.getDefaultSharedPreferences(this@MainActivity)
.edit()
.putBoolean("shown", true)
.apply()
}
}*/
}
private fun selectedFragment(itemId: Int) {
when (itemId) {
R.id.action_album,
R.id.action_artist,
R.id.action_playlist,
R.id.action_song -> setCurrentFragment(LibraryFragment.newInstance(itemId), false)
R.id.action_home -> setCurrentFragment(BannerHomeFragment.newInstance(), false)
else -> {
setCurrentFragment(BannerHomeFragment.newInstance(), false)
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
NavigationUtil.goToSearch(this);
return true
}
return super.onOptionsItemSelected(item)
}
companion object {
const val APP_INTRO_REQUEST = 2323
const val LIBRARY = 1
const val FOLDERS = 3
const val HOME = 0
private const val TAG = "MainActivity"
private const val APP_USER_INFO_REQUEST = 9003
private const val REQUEST_CODE_THEME = 9002
private const val PURCHASE_REQUEST = 101
}
}

View file

@ -0,0 +1,160 @@
package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.util.MusicUtil
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playing_queue.*
class PlayingQueueActivity : AbsMusicServiceActivity() {
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private val upNextAndQueueTime: String
get() = resources.getString(R.string.up_next) + "" + MusicUtil.getReadableDurationString(MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playing_queue)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
setUpRecyclerView()
clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue()
}
}
private fun setUpRecyclerView() {
recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator()
playingQueueAdapter = PlayingQueueAdapter(
this,
MusicPlayerRemote.playingQueue,
MusicPlayerRemote.position,
R.layout.item_queue)
wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(playingQueueAdapter!!)
linearLayoutManager = LinearLayoutManager(this)
recyclerView.apply {
layoutManager = linearLayoutManager
adapter = wrappedAdapter
itemAnimator = animator
recyclerViewDragDropManager!!.attachRecyclerView(this)
}
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
clearQueue.shrink(true)
} else if (dy < 0) {
clearQueue.extend(true)
}
}
})
}
override fun onQueueChanged() {
if (MusicPlayerRemote.playingQueue.isEmpty()) {
finish()
return
}
updateQueue()
updateCurrentSong()
}
override fun onMediaStoreChanged() {
updateQueue()
updateCurrentSong()
}
private fun updateCurrentSong() {}
override fun onPlayingMetaChanged() {
updateQueuePosition()
}
private fun updateQueuePosition() {
playingQueueAdapter!!.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun updateQueue() {
playingQueueAdapter!!.swapDataSet(MusicPlayerRemote.playingQueue, MusicPlayerRemote.position)
resetToCurrentPosition()
}
private fun resetToCurrentPosition() {
recyclerView.stopScroll()
linearLayoutManager.scrollToPositionWithOffset(MusicPlayerRemote.position + 1, 0)
}
override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
super.onPause()
}
override fun onDestroy() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.release()
recyclerViewDragDropManager = null
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter)
wrappedAdapter = null
}
playingQueueAdapter = null
super.onDestroy()
}
private fun setupToolbar() {
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
playerQueueSubHeader.text = upNextAndQueueTime
playerQueueSubHeader.setTextColor(ThemeStore.accentColor(this))
appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this))
toolbar.setBackgroundColor(ThemeStore.primaryColor(this))
toolbar.setNavigationIcon(R.drawable.ic_close_white_24dp)
setSupportActionBar(toolbar)
title = null
toolbar.setNavigationOnClickListener { onBackPressed() }
ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.textColorSecondary(this))
clearQueue.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply {
clearQueue.setTextColor(this)
clearQueue.iconTint = this
}
}
}

View file

@ -0,0 +1,254 @@
package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.loaders.PlaylistLoader
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.mvp.contract.PlaylistSongsContract
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.OrderablePlaylistSongAdapter
import code.name.monkey.retromusic.adapter.song.PlaylistSongAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.util.PlaylistsUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.ViewUtil
import com.afollestad.materialcab.MaterialCab
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator
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 java.util.*
class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsContract.PlaylistSongsView {
private var playlist: Playlist? = null
private var cab: MaterialCab? = null
private lateinit var adapter: SongAdapter
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var songsPresenter: PlaylistSongsPresenter? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColor(Color.TRANSPARENT)
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(this)))
toggleBottomNavigationView(true)
playlist = intent.extras!!.getParcelable(EXTRA_PLAYLIST)
songsPresenter = PlaylistSongsPresenter(this, playlist!!)
setUpToolBar()
setUpRecyclerView()
}
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail)
}
private fun setUpRecyclerView() {
ViewUtil.setUpFastScrollRecyclerViewColor(this, recyclerView, ThemeStore.accentColor(this))
recyclerView.layoutManager = LinearLayoutManager(this)
if (playlist is AbsCustomPlaylist) {
adapter = PlaylistSongAdapter(this, ArrayList(), R.layout.item_list, false, this)
recyclerView!!.adapter = adapter
} else {
recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator()
adapter = OrderablePlaylistSongAdapter(this, ArrayList(), R.layout.item_list, false, this,
object : OrderablePlaylistSongAdapter.OnMoveItemListener {
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (PlaylistsUtil.moveItem(this@PlaylistDetailActivity, playlist!!.id, fromPosition, toPosition)) {
val song = adapter.dataSet.removeAt(fromPosition)
adapter.dataSet.add(toPosition, song)
adapter.notifyItemMoved(fromPosition, toPosition)
}
}
})
wrappedAdapter = recyclerViewDragDropManager!!.createWrappedAdapter(adapter)
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewDragDropManager!!.attachRecyclerView(recyclerView!!)
}
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkIsEmpty()
}
})
recyclerView!!.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
actionShuffleAll.shrink(true)
} else if (dy < 0) {
actionShuffleAll.extend(true)
}
}
})
actionShuffleAll.setOnClickListener {
if (adapter.dataSet.isEmpty()) {
return@setOnClickListener
}
MusicPlayerRemote.openAndShuffleQueue(adapter.dataSet, true)
}
}
override fun onResume() {
super.onResume()
songsPresenter!!.subscribe()
}
private fun setUpToolBar() {
bannerTitle.text = playlist!!.name
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
actionShuffleAll.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply {
actionShuffleAll.setTextColor(this)
actionShuffleAll.iconTint = this
}
val primaryColor = ThemeStore.primaryColor(this)
toolbar!!.apply {
setBackgroundColor(primaryColor)
setSupportActionBar(toolbar)
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.textColorSecondary(this@PlaylistDetailActivity))
}
title = null
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(if (playlist is AbsCustomPlaylist) R.menu.menu_smart_playlist_detail else R.menu.menu_playlist_detail, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when (id) {
android.R.id.home -> {
onBackPressed()
return true
}
}
return PlaylistMenuHelper.handleMenuClick(this, playlist!!, item)
}
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
if (cab != null && cab!!.isActive) {
cab!!.finish()
}
cab = MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this)))
.start(callback)
return cab!!
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive) {
cab!!.finish()
} else {
recyclerView!!.stopScroll()
super.onBackPressed()
}
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
if (playlist !is AbsCustomPlaylist) {
// Playlist deleted
if (!PlaylistsUtil.doesPlaylistExist(this, playlist!!.id)) {
finish()
return
}
// Playlist renamed
val playlistName = PlaylistsUtil.getNameForPlaylist(this, playlist!!.id.toLong())
if (playlistName != playlist!!.name) {
playlist = PlaylistLoader.getPlaylist(this, playlist!!.id).blockingFirst()
setToolbarTitle(playlist!!.name)
}
}
songsPresenter!!.subscribe()
}
private fun setToolbarTitle(title: String) {
supportActionBar!!.title = title
}
private fun checkIsEmpty() {
empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
emptyText.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
}
public override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
super.onPause()
songsPresenter!!.unsubscribe()
}
override fun onDestroy() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.release()
recyclerViewDragDropManager = null
}
if (recyclerView != null) {
recyclerView!!.itemAnimator = null
recyclerView!!.adapter = null
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter)
wrappedAdapter = null
}
super.onDestroy()
}
override fun loading() {}
override fun showEmptyView() {
empty.visibility = View.VISIBLE
emptyText.visibility = View.VISIBLE
}
override fun completed() {}
override fun showData(list: ArrayList<Song>) {
adapter.swapDataSet(list)
}
companion object {
var EXTRA_PLAYLIST = "extra_playlist"
}
}

View file

@ -0,0 +1,160 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.widget.Toast
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails
import kotlinx.android.synthetic.main.activity_pro_version.*
import java.lang.ref.WeakReference
class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
private lateinit var billingProcessor: BillingProcessor
private var restorePurchaseAsyncTask: AsyncTask<*, *, *>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pro_version)
setDrawUnderStatusBar()
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
val primaryColor = ThemeStore.primaryColor(this)
toolbar.setBackgroundColor(primaryColor)
appBarLayout.setBackgroundColor(primaryColor)
toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
setSupportActionBar(toolbar)
title = null
ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.accentColor(this))
restoreButton.isEnabled = false
purchaseButton.isEnabled = false
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
MaterialUtil.setTint(restoreButton, false)
MaterialUtil.setTint(purchaseButton, true)
restoreButton.setOnClickListener {
if (restorePurchaseAsyncTask == null || restorePurchaseAsyncTask!!.status != AsyncTask.Status.RUNNING) {
restorePurchase()
}
}
purchaseButton.setOnClickListener {
billingProcessor.purchase(this@PurchaseActivity, App.PRO_VERSION_PRODUCT_ID)
}
}
private fun restorePurchase() {
if (restorePurchaseAsyncTask != null) {
restorePurchaseAsyncTask!!.cancel(false)
}
restorePurchaseAsyncTask = RestorePurchaseAsyncTask(this).execute()
}
override fun onProductPurchased(productId: String, details: TransactionDetails?) {
Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show()
setResult(RESULT_OK)
}
override fun onPurchaseHistoryRestored() {
if (App.isProVersion) {
Toast.makeText(this, R.string.restored_previous_purchase_please_restart, Toast.LENGTH_LONG).show()
setResult(RESULT_OK)
} else {
Toast.makeText(this, R.string.no_purchase_found, Toast.LENGTH_SHORT).show()
}
}
override fun onBillingError(errorCode: Int, error: Throwable?) {
Log.e(TAG, "Billing error: code = $errorCode", error)
}
override fun onBillingInitialized() {
restoreButton.isEnabled = true
purchaseButton.isEnabled = true
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!billingProcessor.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
override fun onDestroy() {
billingProcessor.release()
super.onDestroy()
}
private class RestorePurchaseAsyncTask internal constructor(purchaseActivity: PurchaseActivity) : AsyncTask<Void, Void, Boolean>() {
private val buyActivityWeakReference: WeakReference<PurchaseActivity> = WeakReference(purchaseActivity)
override fun onPreExecute() {
super.onPreExecute()
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity != null) {
Toast.makeText(purchaseActivity, R.string.restoring_purchase, Toast.LENGTH_SHORT).show()
} else {
cancel(false)
}
}
override fun doInBackground(vararg params: Void): Boolean? {
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity != null) {
return purchaseActivity.billingProcessor.loadOwnedPurchasesFromGoogle()
}
cancel(false)
return null
}
override fun onPostExecute(b: Boolean?) {
super.onPostExecute(b)
val purchaseActivity = buyActivityWeakReference.get()
if (purchaseActivity == null || b == null) {
return
}
if (b) {
purchaseActivity.onPurchaseHistoryRestored()
} else {
Toast.makeText(purchaseActivity, R.string.could_not_restore_purchase, Toast.LENGTH_SHORT).show()
}
}
}
companion object {
private const val TAG: String = "PurchaseActivity"
}
}

View file

@ -0,0 +1,223 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.app.SearchManager
import android.app.Service
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle
import android.speech.RecognizerIntent
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.TextView.BufferType
import android.widget.Toast
import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.mvp.contract.SearchContract
import code.name.monkey.retromusic.mvp.presenter.SearchPresenter
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.SearchAdapter
import code.name.monkey.retromusic.util.RetroUtil
import kotlinx.android.synthetic.main.activity_search.*
import java.util.*
class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, SearchContract.SearchView, TextWatcher {
private lateinit var searchPresenter: SearchPresenter
private var searchAdapter: SearchAdapter? = null
private var query: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
searchPresenter = SearchPresenter(this)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupRecyclerView()
setUpToolBar()
setupSearchView()
if (intent.getBooleanExtra("mic_search", false)) {
startMicSearch()
}
back.setOnClickListener { onBackPressed() }
voiceSearch.setOnClickListener { startMicSearch() }
searchContainer.setCardBackgroundColor(ColorStateList.valueOf(ColorUtil.darkenColor(ThemeStore.primaryColor(this))))
keyboardPopup.setOnClickListener {
val inputManager = getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.showSoftInput(searchView,0)
}
keyboardPopup.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply {
keyboardPopup.setTextColor(this)
keyboardPopup.iconTint = this
}
}
private fun setupRecyclerView() {
searchAdapter = SearchAdapter(this, emptyList())
searchAdapter!!.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
empty.visibility = if (searchAdapter!!.itemCount < 1) View.VISIBLE else View.GONE
}
})
recyclerView.apply {
layoutManager = LinearLayoutManager(this@SearchActivity)
adapter = searchAdapter
}
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy > 0) {
keyboardPopup.shrink(true)
} else if (dy < 0) {
keyboardPopup.extend(true)
}
}
})
}
private fun setupSearchView() {
getSystemService(Context.SEARCH_SERVICE) as SearchManager
searchView.addTextChangedListener(this)
}
override fun onResume() {
super.onResume()
searchPresenter.subscribe()
searchPresenter.search(query)
}
override fun onDestroy() {
super.onDestroy()
searchPresenter.unsubscribe()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(QUERY, query)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
searchPresenter.search(savedInstanceState.getString(QUERY, ""))
}
private fun setUpToolBar() {
title = null
appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this))
}
private fun search(query: String) {
this.query = query.trim { it <= ' ' }
voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE
searchPresenter.search(query)
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
searchPresenter.search(query!!)
}
override fun onQueryTextSubmit(query: String): Boolean {
hideSoftKeyboard()
return false
}
override fun onQueryTextChange(newText: String): Boolean {
search(newText)
return false
}
private fun hideSoftKeyboard() {
RetroUtil.hideSoftKeyboard(this@SearchActivity)
if (searchView != null) {
searchView.clearFocus()
}
}
override fun loading() {
}
override fun showEmptyView() {
searchAdapter!!.swapDataSet(ArrayList())
}
override fun completed() {
}
override fun showData(list: ArrayList<Any>) {
searchAdapter!!.swapDataSet(list)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQ_CODE_SPEECH_INPUT -> {
if (resultCode == Activity.RESULT_OK && null != data) {
val result = data
.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
query = result[0]
searchView.setText(query, BufferType.EDITABLE)
searchPresenter.search(query!!)
}
}
}
}
private fun startMicSearch() {
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.speech_prompt))
try {
startActivityForResult(intent, REQ_CODE_SPEECH_INPUT)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
Toast.makeText(this, getString(R.string.speech_not_supported), Toast.LENGTH_SHORT).show()
}
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(newText: CharSequence, start: Int, before: Int, count: Int) {
search(newText.toString())
}
override fun afterTextChanged(s: Editable) {
}
companion object {
val TAG: String = SearchActivity::class.java.simpleName
const val QUERY: String = "query"
private const val REQ_CODE_SPEECH_INPUT = 9002
}
}

View file

@ -0,0 +1,125 @@
package code.name.monkey.retromusic.activities
import android.content.SharedPreferences
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.fragments.settings.MainSettingsFragment
import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.android.synthetic.main.activity_settings.*
class SettingsActivity : AbsBaseActivity(), /*ColorChooserDialog.ColorCallback,*/ SharedPreferences.OnSharedPreferenceChangeListener {
private val fragmentManager = supportFragmentManager
/* override fun onColorSelection(dialog: ColorChooserDialog, @ColorInt selectedColor: Int) {
when (dialog.title) {
R.string.primary_color -> {
val theme = if (ColorUtil.isColorLight(selectedColor))
PreferenceUtil.getThemeResFromPrefValue("light")
else
PreferenceUtil.getThemeResFromPrefValue("dark")
ThemeStore.editTheme(this).activityTheme(theme).primaryColor(selectedColor).commit()
}
R.string.accent_color -> ThemeStore.editTheme(this).accentColor(selectedColor).commit()
}
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
recreate()
}
override fun onColorChooserDismissed(dialog: ColorChooserDialog) {
}*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setupToolbar()
if (savedInstanceState == null) {
fragmentManager.beginTransaction().replace(R.id.contentFrame, MainSettingsFragment())
.commit()
}
}
private fun setupToolbar() {
setSupportActionBar(toolbar)
title = null
toolbar.apply {
setBackgroundColor(ThemeStore.primaryColor(context))
setNavigationOnClickListener { onBackPressed() }
ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.textColorSecondary(context))
}
appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this))
settingsTitle.setTextColor(ThemeStore.textColorPrimary(this))
}
fun setupFragment(fragment: Fragment, @StringRes titleName: Int) {
val fragmentTransaction = fragmentManager
.beginTransaction()
.setCustomAnimations(R.anim.sliding_in_left, R.anim.sliding_out_right, android.R.anim.slide_in_left, android.R.anim.slide_out_right)
settingsTitle.setText(titleName)
if (detailContentFrame == null) {
fragmentTransaction.replace(R.id.contentFrame, fragment, fragment.tag)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
} else {
fragmentTransaction.replace(R.id.detailContentFrame, fragment, fragment.tag)
fragmentTransaction.commit()
}
}
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount == 0) {
super.onBackPressed()
} else {
settingsTitle.setText(R.string.action_settings)
fragmentManager.popBackStack()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
public override fun onPause() {
super.onPause()
PreferenceUtil.getInstance().unregisterOnSharedPreferenceChangedListener(this)
}
public override fun onResume() {
super.onResume()
PreferenceUtil.getInstance().registerOnSharedPreferenceChangedListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (key == PreferenceUtil.PROFILE_IMAGE_PATH) {
recreate()
}
}
companion object {
const val TAG: String = "SettingsActivity"
}
}

View file

@ -0,0 +1,246 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.graphics.Paint
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.views.IconImageView
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.SkuDetails
import com.anjlab.android.iab.v3.TransactionDetails
import kotlinx.android.synthetic.main.activity_donation.*
import java.lang.ref.WeakReference
import java.util.*
class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
companion object {
val TAG: String = SupportDevelopmentActivity::class.java.simpleName
const val DONATION_PRODUCT_IDS = R.array.donation_ids
private const val TEZ_REQUEST_CODE = 123
}
var billingProcessor: BillingProcessor? = null
private var skuDetailsLoadAsyncTask: AsyncTask<*, *, *>? = null
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
fun donate(i: Int) {
val ids = resources.getStringArray(DONATION_PRODUCT_IDS)
billingProcessor!!.purchase(this, ids[i])
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_donation)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
TintHelper.setTint(progress, ThemeStore.accentColor(this))
donation.setTextColor(ThemeStore.accentColor(this))
}
private fun setupToolbar() {
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
val primaryColor = ThemeStore.primaryColor(this)
appBarLayout.setBackgroundColor(primaryColor)
toolbar.setBackgroundColor(primaryColor)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
setSupportActionBar(toolbar)
title = null
ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.textColorSecondary(this))
}
override fun onBillingInitialized() {
loadSkuDetails()
}
private fun loadSkuDetails() {
if (skuDetailsLoadAsyncTask != null) {
skuDetailsLoadAsyncTask!!.cancel(false)
}
skuDetailsLoadAsyncTask = SkuDetailsLoadAsyncTask(this).execute()
}
override fun onProductPurchased(productId: String, details: TransactionDetails?) {
//loadSkuDetails();
Toast.makeText(this, R.string.thank_you, Toast.LENGTH_SHORT).show()
}
override fun onBillingError(errorCode: Int, error: Throwable?) {
Log.e(TAG, "Billing error: code = $errorCode", error)
}
override fun onPurchaseHistoryRestored() {
//loadSkuDetails();
Toast.makeText(this, R.string.restored_previous_purchases, Toast.LENGTH_SHORT).show()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!billingProcessor!!.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data)
}
if (requestCode == TEZ_REQUEST_CODE) {
// Process based on the data in response.
Log.d("result", data!!.getStringExtra("Status"))
}
}
override fun onDestroy() {
if (billingProcessor != null) {
billingProcessor!!.release()
}
if (skuDetailsLoadAsyncTask != null) {
skuDetailsLoadAsyncTask!!.cancel(true)
}
super.onDestroy()
}
}
private class SkuDetailsLoadAsyncTask internal constructor(supportDevelopmentActivity: SupportDevelopmentActivity) : AsyncTask<Void, Void, List<SkuDetails>>() {
private val weakReference: WeakReference<SupportDevelopmentActivity> = WeakReference(supportDevelopmentActivity)
override fun onPreExecute() {
super.onPreExecute()
val supportDevelopmentActivity = weakReference.get() ?: return
supportDevelopmentActivity.progressContainer.visibility = View.VISIBLE
supportDevelopmentActivity.recyclerView.visibility = View.GONE
}
override fun doInBackground(vararg params: Void): List<SkuDetails>? {
val dialog = weakReference.get()
if (dialog != null) {
val ids = dialog.resources.getStringArray(SupportDevelopmentActivity.DONATION_PRODUCT_IDS)
return dialog.billingProcessor!!.getPurchaseListingDetails(ArrayList(Arrays.asList(*ids)))
}
cancel(false)
return null
}
override fun onPostExecute(skuDetails: List<SkuDetails>?) {
super.onPostExecute(skuDetails)
val dialog = weakReference.get() ?: return
if (skuDetails == null || skuDetails.isEmpty()) {
dialog.progressContainer.visibility = View.GONE
return
}
dialog.progressContainer.visibility = View.GONE
dialog.recyclerView.itemAnimator = DefaultItemAnimator()
dialog.recyclerView.layoutManager = GridLayoutManager(dialog, 2)
dialog.recyclerView.adapter = SkuDetailsAdapter(dialog, skuDetails)
dialog.recyclerView.visibility = View.VISIBLE
}
}
class SkuDetailsAdapter(private var donationsDialog: SupportDevelopmentActivity, objects: List<SkuDetails>) : RecyclerView.Adapter<SkuDetailsAdapter.ViewHolder>() {
private var skuDetailsList: List<SkuDetails> = ArrayList()
init {
skuDetailsList = objects
}
private fun getIcon(position: Int): Int {
return when (position) {
0 -> R.drawable.ic_cookie_white_24dp
1 -> R.drawable.ic_take_away_white_24dp
2 -> R.drawable.ic_take_away_coffe_white_24dp
3 -> R.drawable.ic_beer_white_24dp
4 -> R.drawable.ic_fast_food_meal_white_24dp
5 -> R.drawable.ic_popcorn_white_24dp
6 -> R.drawable.ic_card_giftcard_white_24dp
else -> R.drawable.ic_card_giftcard_white_24dp
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(donationsDialog).inflate(LAYOUT_RES_ID, viewGroup, false))
}
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val skuDetails = skuDetailsList[i]
viewHolder.title.text = skuDetails.title.replace("(Retro Music Player)", "").trim { it <= ' ' }
viewHolder.text.text = skuDetails.description
viewHolder.text.visibility = View.GONE
viewHolder.price.text = skuDetails.priceText
viewHolder.image.setImageResource(getIcon(i))
val purchased = donationsDialog.billingProcessor!!.isPurchased(skuDetails.productId)
val titleTextColor = if (purchased) ATHUtil.resolveColor(donationsDialog, android.R.attr.textColorHint) else ThemeStore.textColorPrimary(donationsDialog)
val contentTextColor = if (purchased) titleTextColor else ThemeStore.textColorSecondary(donationsDialog)
viewHolder.title.setTextColor(titleTextColor)
viewHolder.text.setTextColor(contentTextColor)
viewHolder.price.setTextColor(titleTextColor)
strikeThrough(viewHolder.title, purchased)
strikeThrough(viewHolder.text, purchased)
strikeThrough(viewHolder.price, purchased)
viewHolder.itemView.setOnTouchListener { _, _ -> purchased }
viewHolder.itemView.setOnClickListener { donationsDialog.donate(i) }
}
override fun getItemCount(): Int {
return skuDetailsList.size
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var title: TextView = view.findViewById(R.id.itemTitle)
var text: TextView = view.findViewById(R.id.itemText)
var price: TextView = view.findViewById(R.id.itemPrice)
var image: IconImageView = view.findViewById(R.id.itemImage)
}
companion object {
@LayoutRes
private val LAYOUT_RES_ID = R.layout.item_donation_option
private fun strikeThrough(textView: TextView, strikeThrough: Boolean) {
textView.paintFlags = if (strikeThrough)
textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
textView.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
}

View file

@ -0,0 +1,368 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.content.*
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.MediaStore.Images.Media.getBitmap
import android.text.TextUtils
import android.view.MenuItem
import android.widget.Toast
import androidx.core.content.FileProvider
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.*
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.Constants.USER_BANNER
import code.name.monkey.retromusic.Constants.USER_PROFILE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.util.Compressor
import code.name.monkey.retromusic.util.ImageUtil.getResizedBitmap
import code.name.monkey.retromusic.util.PreferenceUtil
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_user_info.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class UserInfoActivity : AbsBaseActivity() {
private var disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_info)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
MaterialUtil.setTint(nameContainer, false)
MaterialUtil.setTint(bioContainer, false)
name.setText(PreferenceUtil.getInstance().userName)
bio.setText(PreferenceUtil.getInstance().userBio)
if (!PreferenceUtil.getInstance().profileImage.isEmpty()) {
loadImageFromStorage(PreferenceUtil.getInstance().profileImage)
}
if (!PreferenceUtil.getInstance().bannerImage.isEmpty()) {
loadBannerFromStorage(PreferenceUtil.getInstance().bannerImage)
}
userImage.setOnClickListener {
MaterialDialog(this).show {
title(text = getString(R.string.set_photo))
listItems(items = listOf(getString(R.string.new_profile_photo), getString(R.string.remove_profile_photo))) { _, position, _ ->
when (position) {
0 -> pickNewPhoto()
1 -> PreferenceUtil.getInstance().saveProfileImage("")
}
}
}
}
bannerSelect.setOnClickListener {
showBannerOptions()
}
next.setOnClickListener {
val nameString = name.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(nameString)) {
Toast.makeText(this, "Umm name is empty", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val bioString = bio.text.toString().trim() { it <= ' ' }
if (TextUtils.isEmpty(bioString)) {
Toast.makeText(this, "Umm bio is empty", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
PreferenceUtil.getInstance().userName = nameString
PreferenceUtil.getInstance().userBio = bioString
setResult(Activity.RESULT_OK)
finish()
}
next.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply {
next.setTextColor(this)
next.iconTint = this
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
private fun setupToolbar() {
val primaryColor = ThemeStore.primaryColor(this)
toolbar.apply {
setNavigationIcon(R.drawable.ic_keyboard_backspace_black_24dp)
setBackgroundColor(primaryColor)
ToolbarContentTintHelper.colorBackButton(this, ThemeStore.textColorSecondary(this@UserInfoActivity))
setSupportActionBar(this)
}
appBarLayout.setBackgroundColor(primaryColor)
title = null
}
private fun showBannerOptions() {
MaterialDialog(this).show {
title(R.string.select_banner_photo)
listItems(items = listOf(getString(R.string.new_banner_photo), getString(R.string.remove_banner_photo)))
{ _, position, _ ->
when (position) {
0 -> selectBannerImage()
1 -> PreferenceUtil.getInstance().setBannerImagePath("")
}
}
}
}
private fun selectBannerImage() {
if (PreferenceUtil.getInstance().bannerImage.isEmpty()) {
val pickImageIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
pickImageIntent.type = "image/*"
pickImageIntent.putExtra("crop", "true")
pickImageIntent.putExtra("outputX", 1290)
pickImageIntent.putExtra("outputY", 720)
pickImageIntent.putExtra("aspectX", 16)
pickImageIntent.putExtra("aspectY", 9)
pickImageIntent.putExtra("scale", true)
//intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(pickImageIntent,
"Select Picture"), PICK_BANNER_REQUEST)
} else {
PreferenceUtil.getInstance().setBannerImagePath("")
bannerImage.setImageResource(android.R.color.transparent)
}
}
private fun pickNewPhoto() {
val pickImageIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
pickImageIntent.type = "image/*"
pickImageIntent.putExtra("crop", "true")
pickImageIntent.putExtra("outputX", 512)
pickImageIntent.putExtra("outputY", 512)
pickImageIntent.putExtra("aspectX", 1)
pickImageIntent.putExtra("aspectY", 1)
pickImageIntent.putExtra("scale", true)
startActivityForResult(Intent.createChooser(pickImageIntent, "Select Picture"), PICK_IMAGE_REQUEST)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
PICK_IMAGE_REQUEST -> {
val uri = data!!.data
try {
val bitmap = getResizedBitmap(getBitmap(contentResolver, uri), PROFILE_ICON_SIZE)
val profileImagePath = saveToInternalStorage(bitmap, USER_PROFILE)
PreferenceUtil.getInstance().saveProfileImage(profileImagePath)
loadImageFromStorage(profileImagePath)
} catch (e: IOException) {
e.printStackTrace()
}
}
CROP_IMAGE_REQUEST -> {
val extras: Bundle = data?.extras!!
val selectedBitmap: Bitmap = extras.getParcelable("data");
val profileImagePath = saveToInternalStorage(selectedBitmap, USER_PROFILE)
PreferenceUtil.getInstance().saveProfileImage(profileImagePath)
loadImageFromStorage(profileImagePath)
}
PICK_BANNER_REQUEST -> {
val uri = data?.data
try {
val bitmap = getBitmap(contentResolver, uri)
val profileImagePath = saveToInternalStorage(bitmap, USER_BANNER)
PreferenceUtil.getInstance().setBannerImagePath(profileImagePath)
loadBannerFromStorage(profileImagePath)
} catch (e: IOException) {
e.printStackTrace()
}
}
CROP_BANNER_REQUEST -> {
val extras: Bundle = data?.extras!!
val selectedBitmap: Bitmap = extras.getParcelable("data");
val profileImagePath = saveToInternalStorage(selectedBitmap, USER_BANNER)
PreferenceUtil.getInstance().saveProfileImage(profileImagePath)
loadImageFromStorage(profileImagePath)
}
}
}
/*if (requestCode == PICK_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null && data.data != null) {
val picturePath = data.data
performCrop(picturePath)
} else if (requestCode == CROP_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null && data.data != null) {
val uri = data.data
try {
val bitmap = ImageUtil.getResizedBitmap(Media.getBitmap(contentResolver, uri), PROFILE_ICON_SIZE)
val profileImagePath = saveToInternalStorage(bitmap, USER_PROFILE)
PreferenceUtil.getInstance().saveProfileImage(profileImagePath)
loadImageFromStorage(profileImagePath)
} catch (e: IOException) {
e.printStackTrace()
}
} else if (requestCode == PICK_BANNER_REQUEST && resultCode == Activity.RESULT_OK && data != null &&
data.data != null) {
val uri = data.data
try {
val bitmap = Media.getBitmap(contentResolver, uri)
val profileImagePath = saveToInternalStorage(bitmap, USER_BANNER)
PreferenceUtil.getInstance().setBannerImagePath(profileImagePath)
loadBannerFromStorage(profileImagePath)
} catch (e: IOException) {
e.printStackTrace()
}
}*/
}
private fun getImagePathFromUri(aUri: Uri?): String? {
var imagePath: String? = null
if (aUri == null) {
return imagePath
}
if (DocumentsContract.isDocumentUri(App.context, aUri)) {
val documentId = DocumentsContract.getDocumentId(aUri)
if ("com.android.providers.media.documents" == aUri.authority) {
val id = documentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]
val selection = MediaStore.Images.Media._ID + "=" + id
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection)
} else if ("com.android.providers.downloads.documents" == aUri.authority) {
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),
java.lang.Long.valueOf(documentId))
imagePath = getImagePath(contentUri, null)
}
} else if ("content".equals(aUri.scheme!!, ignoreCase = true)) {
imagePath = getImagePath(aUri, null)
} else if ("file".equals(aUri.scheme!!, ignoreCase = true)) {
imagePath = aUri.path
}
return imagePath
}
private fun getImagePath(aUri: Uri, aSelection: String?): String? {
var path: String? = null
val cursor = App.context.contentResolver.query(aUri, null, aSelection, null, null)
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA))
}
cursor.close()
}
return path
}
private fun performBannerCrop(picturePath: Uri?) {
val photoUri = FileProvider.getUriForFile(this, "$packageName.provider", File(getImagePathFromUri(picturePath)))
try {
val cropIntent = Intent("com.android.camera.action.CROP")
cropIntent.setDataAndType(photoUri, "image/*")
cropIntent.putExtra("crop", "true")
cropIntent.putExtra("aspectX", 1)
cropIntent.putExtra("aspectY", 1)
cropIntent.putExtra("return-data", true)
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(cropIntent, CROP_BANNER_REQUEST)
} catch (anfe: ActivityNotFoundException) {
val errorMessage = "your device doesn't support the crop action!"
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
}
}
private fun performCrop(imageUri: Uri) {
val photoUri = FileProvider.getUriForFile(this, "$packageName.provider", File(getImagePathFromUri(imageUri)))
try {
val cropIntent = Intent("com.android.camera.action.CROP")
cropIntent.setDataAndType(photoUri, "image/*")
cropIntent.putExtra("crop", "true")
cropIntent.putExtra("aspectX", 1)
cropIntent.putExtra("aspectY", 1)
cropIntent.putExtra("outputX", 280)
cropIntent.putExtra("outputY", 280)
cropIntent.putExtra("return-data", true)
cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(cropIntent, CROP_IMAGE_REQUEST)
} catch (anfe: ActivityNotFoundException) {
val errorMessage = "your device doesn't support the crop action!"
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
}
}
private fun loadBannerFromStorage(profileImagePath: String) {
disposable.add(Compressor(this)
.setQuality(100)
.setCompressFormat(Bitmap.CompressFormat.WEBP)
.compressToBitmapAsFlowable(File(profileImagePath, USER_BANNER))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { bitmap -> bannerImage.setImageBitmap(bitmap) })
}
private fun loadImageFromStorage(path: String) {
disposable.add(Compressor(this)
.setMaxHeight(300)
.setMaxWidth(300)
.setQuality(75)
.setCompressFormat(Bitmap.CompressFormat.WEBP)
.compressToBitmapAsFlowable(File(path, USER_PROFILE))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { bitmap -> userImage!!.setImageBitmap(bitmap) })
}
private fun saveToInternalStorage(bitmapImage: Bitmap, userBanner: String): String {
val cw = ContextWrapper(this)
val directory = cw.getDir("imageDir", Context.MODE_PRIVATE)
val myPath = File(directory, userBanner)
var fos: FileOutputStream? = null
try {
fos = FileOutputStream(myPath)
// Use the compress method on the BitMap object to write image to the OutputStream
bitmapImage.compress(Bitmap.CompressFormat.WEBP, 100, fos)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
fos?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
return directory.absolutePath
}
companion object {
private const val PICK_IMAGE_REQUEST = 9002
private const val CROP_IMAGE_REQUEST = 9003
private const val PICK_BANNER_REQUEST = 9004
private const val CROP_BANNER_REQUEST = 9005
private const val PROFILE_ICON_SIZE = 400
}
}
fun Activity.pickImage(requestCode: Int) {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
startActivityForResult(this, requestCode)
}
}

View file

@ -0,0 +1,95 @@
package code.name.monkey.retromusic.activities;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.webkit.WebView;
import android.widget.TextView;
import com.google.android.material.appbar.AppBarLayout;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
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.ToolbarContentTintHelper;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.base.AbsBaseActivity;
import code.name.monkey.retromusic.util.PreferenceUtil;
public class WhatsNewActivity extends AbsBaseActivity {
WebView webView;
TextView title;
Toolbar toolbar;
AppBarLayout appBarLayout;
private static void setChangelogRead(@NonNull Context context) {
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
int currentVersion = pInfo.versionCode;
PreferenceUtil.getInstance().setLastChangeLogVersion(currentVersion);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
private static String colorToHex(int color) {
return Integer.toHexString(color).substring(2);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_whats_new);
webView = findViewById(R.id.webView);
title = findViewById(R.id.bannerTitle);
toolbar = findViewById(R.id.toolbar);
appBarLayout = findViewById(R.id.appBarLayout);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
toolbar.setBackgroundColor(ThemeStore.Companion.primaryColor(this));
appBarLayout.setBackgroundColor(ThemeStore.Companion.primaryColor(this));
setSupportActionBar(toolbar);
setTitle(null);
toolbar.setNavigationOnClickListener(v -> onBackPressed());
title.setTextColor(ThemeStore.Companion.textColorPrimary(this));
ToolbarContentTintHelper.colorBackButton(toolbar, ThemeStore.Companion.textColorSecondary(this));
try {
// Load from phonograph-changelog.html in the assets folder
StringBuilder buf = new StringBuilder();
InputStream json = getAssets().open("retro-changelog.html");
BufferedReader in = new BufferedReader(new InputStreamReader(json, "UTF-8"));
String str;
while ((str = in.readLine()) != null)
buf.append(str);
in.close();
// Inject color values for WebView body background and links
final String backgroundColor = colorToHex(ThemeStore.Companion.primaryColor(this));
final String contentColor = ATHUtil.INSTANCE.isWindowBackgroundDark(this) ? "#ffffff" : "#000000";
webView.loadData(buf.toString()
.replace("{style-placeholder}",
String.format("body { background-color: %s; color: %s; }", backgroundColor, contentColor))
.replace("{link-color}", colorToHex(ThemeStore.Companion.accentColor(this)))
.replace("{link-color-active}", colorToHex(ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))))
, "text/html", "UTF-8");
} catch (Throwable e) {
webView.loadData("<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
}
setChangelogRead(this);
}
}

View file

@ -0,0 +1,138 @@
package code.name.monkey.retromusic.activities.base
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.KeyEvent
import android.view.View
import androidx.core.app.ActivityCompat
import code.name.monkey.appthemehelper.ThemeStore
import com.google.android.material.snackbar.Snackbar
abstract class AbsBaseActivity : AbsThemeActivity() {
private var hadPermissions: Boolean = false
private lateinit var permissions: Array<String>
private var permissionDeniedMessage: String? = null
open fun getPermissionsToRequest(): Array<String> {
return arrayOf()
}
protected fun setPermissionDeniedMessage(message: String) {
permissionDeniedMessage = message
}
fun getPermissionDeniedMessage(): String {
return if (permissionDeniedMessage == null) getString(code.name.monkey.retromusic.R.string.permissions_denied) else permissionDeniedMessage!!
}
private val snackBarContainer: View
get() = window.decorView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
volumeControlStream = AudioManager.STREAM_MUSIC
permissions = getPermissionsToRequest()
hadPermissions = hasPermissions()
permissionDeniedMessage = null
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
if (!hasPermissions()) {
requestPermissions()
}
}
override fun onResume() {
super.onResume()
val hasPermissions = hasPermissions()
if (hasPermissions != hadPermissions) {
hadPermissions = hasPermissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
onHasPermissionsChanged(hasPermissions)
}
}
}
protected open fun onHasPermissionsChanged(hasPermissions: Boolean) {
// implemented by sub classes
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == KeyEvent.KEYCODE_MENU && event.action == KeyEvent.ACTION_UP) {
showOverflowMenu()
return true
}
return super.dispatchKeyEvent(event)
}
protected fun showOverflowMenu() {
}
protected open fun requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, PERMISSION_REQUEST)
}
}
protected fun hasPermissions(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (permission in permissions) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
return false
}
}
}
return true
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST) {
for (grantResult in grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this@AbsBaseActivity,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
//User has deny from permission dialog
Snackbar.make(snackBarContainer, permissionDeniedMessage!!,
Snackbar.LENGTH_INDEFINITE)
.setAction(code.name.monkey.retromusic.R.string.action_grant) { requestPermissions() }
.setActionTextColor(ThemeStore.accentColor(this))
.show()
} else {
// User has deny permission and checked never show permission dialog so you can redirect to Application settings page
Snackbar.make(snackBarContainer, permissionDeniedMessage!!,
Snackbar.LENGTH_INDEFINITE)
.setAction(code.name.monkey.retromusic.R.string.action_settings) {
val intent = Intent()
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", this@AbsBaseActivity.packageName, null)
intent.data = uri
startActivity(intent)
}
.setActionTextColor(ThemeStore.accentColor(this))
.show()
}
return
}
}
hadPermissions = true
onHasPermissionsChanged(true)
}
}
companion object {
const val PERMISSION_REQUEST = 100
}
}

View file

@ -0,0 +1,12 @@
package code.name.monkey.retromusic.activities.base
import android.os.Bundle
import code.name.monkey.appthemehelper.ATHActivity
import code.name.monkey.retromusic.helper.TopExceptionHandler
abstract class AbsCrashCollector : ATHActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread.setDefaultUncaughtExceptionHandler(TopExceptionHandler())
}
}

View file

@ -0,0 +1,168 @@
package code.name.monkey.retromusic.activities.base
import android.Manifest
import android.content.*
import android.os.Bundle
import android.os.IBinder
import code.name.monkey.retromusic.Constants.MEDIA_STORE_CHANGED
import code.name.monkey.retromusic.Constants.META_CHANGED
import code.name.monkey.retromusic.Constants.PLAY_STATE_CHANGED
import code.name.monkey.retromusic.Constants.QUEUE_CHANGED
import code.name.monkey.retromusic.Constants.REPEAT_MODE_CHANGED
import code.name.monkey.retromusic.Constants.SHUFFLE_MODE_CHANGED
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import java.lang.ref.WeakReference
import java.util.*
abstract class AbsMusicServiceActivity : AbsBaseActivity(), MusicServiceEventListener {
private val mMusicServiceEventListeners = ArrayList<MusicServiceEventListener>()
private var serviceToken: MusicPlayerRemote.ServiceToken? = null
private var musicStateReceiver: MusicStateReceiver? = null
private var receiverRegistered: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
serviceToken = MusicPlayerRemote.bindToService(this, object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
this@AbsMusicServiceActivity.onServiceConnected()
}
override fun onServiceDisconnected(name: ComponentName) {
this@AbsMusicServiceActivity.onServiceDisconnected()
}
})
setPermissionDeniedMessage(getString(R.string.permission_external_storage_denied));
}
override fun onDestroy() {
super.onDestroy()
MusicPlayerRemote.unbindFromService(serviceToken)
if (receiverRegistered) {
unregisterReceiver(musicStateReceiver)
receiverRegistered = false
}
}
fun addMusicServiceEventListener(listener: MusicServiceEventListener?) {
if (listener != null) {
mMusicServiceEventListeners.add(listener)
}
}
fun removeMusicServiceEventListener(listener: MusicServiceEventListener?) {
if (listener != null) {
mMusicServiceEventListeners.remove(listener)
}
}
override fun onServiceConnected() {
if (!receiverRegistered) {
musicStateReceiver = MusicStateReceiver(this)
val filter = IntentFilter()
filter.addAction(PLAY_STATE_CHANGED)
filter.addAction(SHUFFLE_MODE_CHANGED)
filter.addAction(REPEAT_MODE_CHANGED)
filter.addAction(META_CHANGED)
filter.addAction(QUEUE_CHANGED)
filter.addAction(MEDIA_STORE_CHANGED)
registerReceiver(musicStateReceiver, filter)
receiverRegistered = true
}
for (listener in mMusicServiceEventListeners) {
listener.onServiceConnected()
}
}
override fun onServiceDisconnected() {
if (receiverRegistered) {
unregisterReceiver(musicStateReceiver)
receiverRegistered = false
}
for (listener in mMusicServiceEventListeners) {
listener.onServiceDisconnected()
}
}
override fun onPlayingMetaChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onPlayingMetaChanged()
}
}
override fun onQueueChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onQueueChanged()
}
}
override fun onPlayStateChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onPlayStateChanged()
}
}
override fun onMediaStoreChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onMediaStoreChanged()
}
}
override fun onRepeatModeChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onRepeatModeChanged()
}
}
override fun onShuffleModeChanged() {
for (listener in mMusicServiceEventListeners) {
listener.onShuffleModeChanged()
}
}
override fun onHasPermissionsChanged(hasPermissions: Boolean) {
super.onHasPermissionsChanged(hasPermissions)
val intent = Intent(MEDIA_STORE_CHANGED)
intent.putExtra("from_permissions_changed", true) // just in case we need to know this at some point
sendBroadcast(intent)
}
override fun getPermissionsToRequest(): Array<String> {
return arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
private class MusicStateReceiver(activity: AbsMusicServiceActivity) : BroadcastReceiver() {
private val reference: WeakReference<AbsMusicServiceActivity> = WeakReference(activity)
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
val activity = reference.get()
if (activity != null && action != null) {
when (action) {
META_CHANGED -> activity.onPlayingMetaChanged()
QUEUE_CHANGED -> activity.onQueueChanged()
PLAY_STATE_CHANGED -> activity.onPlayStateChanged()
REPEAT_MODE_CHANGED -> activity.onRepeatModeChanged()
SHUFFLE_MODE_CHANGED -> activity.onShuffleModeChanged()
MEDIA_STORE_CHANGED -> activity.onMediaStoreChanged()
}
}
}
}
companion object {
val TAG: String = AbsMusicServiceActivity::class.java.simpleName
}
}

View file

@ -0,0 +1,309 @@
package code.name.monkey.retromusic.activities.base
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.fragments.MiniPlayerFragment
import code.name.monkey.retromusic.fragments.NowPlayingScreen
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.player.adaptive.AdaptiveFragment
import code.name.monkey.retromusic.fragments.player.blur.BlurPlayerFragment
import code.name.monkey.retromusic.fragments.player.card.CardFragment
import code.name.monkey.retromusic.fragments.player.cardblur.CardBlurFragment
import code.name.monkey.retromusic.fragments.player.color.ColorFragment
import code.name.monkey.retromusic.fragments.player.fit.FitFragment
import code.name.monkey.retromusic.fragments.player.flat.FlatPlayerFragment
import code.name.monkey.retromusic.fragments.player.full.FullPlayerFragment
import code.name.monkey.retromusic.fragments.player.material.MaterialFragment
import code.name.monkey.retromusic.fragments.player.normal.PlayerFragment
import code.name.monkey.retromusic.fragments.player.plain.PlainPlayerFragment
import code.name.monkey.retromusic.fragments.player.simple.SimplePlayerFragment
import code.name.monkey.retromusic.fragments.player.tiny.TinyPlayerFragment
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.views.BottomNavigationBarTinted
import com.sothree.slidinguppanel.SlidingUpPanelLayout
import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(), SlidingUpPanelLayout.PanelSlideListener, AbsPlayerFragment.Callbacks {
companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
}
private var miniPlayerFragment: MiniPlayerFragment? = null
private var playerFragment: AbsPlayerFragment? = null
private var currentNowPlayingScreen: NowPlayingScreen? = null
private var navigationBarColor: Int = 0
private var taskColor: Int = 0
private var lightStatusBar: Boolean = false
private var lightNavigationBar: Boolean = false
private var navigationBarColorAnimator: ValueAnimator? = null
protected abstract fun createContentView(): View
val panelState: SlidingUpPanelLayout.PanelState?
get() = slidingLayout.panelState
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(createContentView())
chooseFragmentForTheme()
setupSlidingUpPanel()
}
override fun onResume() {
super.onResume()
if (currentNowPlayingScreen != PreferenceUtil.getInstance().nowPlayingScreen) {
postRecreate()
}
}
override fun onDestroy() {
super.onDestroy()
if (navigationBarColorAnimator != null) navigationBarColorAnimator!!.cancel() // just in case
}
protected fun wrapSlidingMusicPanel(@LayoutRes resId: Int): View {
@SuppressLint("InflateParams")
val slidingMusicPanelLayout = layoutInflater.inflate(R.layout.sliding_music_panel_layout, null)
val contentContainer = slidingMusicPanelLayout.findViewById<ViewGroup>(R.id.mainContentFrame)
layoutInflater.inflate(resId, contentContainer)
return slidingMusicPanelLayout
}
fun setAntiDragView(antiDragView: View) {
slidingLayout.setAntiDragView(antiDragView)
}
private fun collapsePanel() {
slidingLayout.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED
}
fun expandPanel() {
slidingLayout.panelState = SlidingUpPanelLayout.PanelState.EXPANDED
}
private fun setMiniPlayerAlphaProgress(progress: Float) {
if (miniPlayerFragment!!.view == null) return
val alpha = 1 - progress
miniPlayerFragment!!.view!!.alpha = alpha
// necessary to make the views below clickable
miniPlayerFragment!!.view!!.visibility = if (alpha == 0f) View.GONE else View.VISIBLE
bottomNavigationView.translationY = progress * 500
bottomNavigationView.alpha = alpha
}
open fun onPanelCollapsed() {
// restore values
super.setLightStatusbar(lightStatusBar)
super.setTaskDescriptionColor(taskColor)
super.setNavigationbarColor(navigationBarColor)
super.setLightNavigationBar(lightNavigationBar)
playerFragment!!.setMenuVisibility(false)
playerFragment!!.userVisibleHint = false
playerFragment!!.onHide()
}
open fun onPanelExpanded() {
val playerFragmentColor = playerFragment!!.paletteColor
super.setTaskDescriptionColor(playerFragmentColor)
playerFragment!!.setMenuVisibility(true)
playerFragment!!.userVisibleHint = true
playerFragment!!.onShow()
onPaletteColorChanged()
}
private fun setupSlidingUpPanel() {
slidingLayout.viewTreeObserver
.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
slidingLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
if (panelState == SlidingUpPanelLayout.PanelState.EXPANDED) {
onPanelSlide(slidingLayout, 1f)
onPanelExpanded()
} else if (panelState == SlidingUpPanelLayout.PanelState.COLLAPSED) {
onPanelCollapsed()
} else {
playerFragment!!.onHide()
}
}
})
slidingLayout.addPanelSlideListener(this)
}
fun toggleBottomNavigationView(toggle: Boolean) {
bottomNavigationView.visibility = if (toggle) View.GONE else View.VISIBLE
}
fun getBottomNavigationView(): BottomNavigationBarTinted {
return bottomNavigationView
}
fun hideBottomBar(hide: Boolean) {
val heightOfBar = resources.getDimensionPixelSize(R.dimen.mini_player_height)
val heightOfBarWithTabs = resources.getDimensionPixelSize(R.dimen.mini_player_height_expanded)
if (hide) {
slidingLayout.panelHeight = 0
collapsePanel()
} else {
if (!MusicPlayerRemote.playingQueue.isEmpty()) {
slidingLayout.panelHeight = if (bottomNavigationView.visibility == View.VISIBLE) heightOfBarWithTabs else heightOfBar
}
}
}
fun setBottomBarVisibility(gone: Int) {
bottomNavigationView.visibility = gone
hideBottomBar(false)
}
private fun chooseFragmentForTheme() {
currentNowPlayingScreen = PreferenceUtil.getInstance().nowPlayingScreen
val fragment: Fragment = when (currentNowPlayingScreen) {
BLUR -> BlurPlayerFragment()
ADAPTIVE -> AdaptiveFragment()
NORMAL -> PlayerFragment()
CARD -> CardFragment()
BLUR_CARD -> CardBlurFragment()
FIT -> FitFragment()
FLAT -> FlatPlayerFragment()
FULL -> FullPlayerFragment()
PLAIN -> PlainPlayerFragment()
SIMPLE -> SimplePlayerFragment()
MATERIAL -> MaterialFragment()
COLOR -> ColorFragment()
TINY -> TinyPlayerFragment()
//SLIDE -> SlidePlayerFragment()
else -> PlayerFragment()
} // must implement AbsPlayerFragment
supportFragmentManager.beginTransaction().replace(R.id.playerFragmentContainer, fragment).commit()
supportFragmentManager.executePendingTransactions()
playerFragment = supportFragmentManager.findFragmentById(R.id.playerFragmentContainer) as AbsPlayerFragment
miniPlayerFragment = supportFragmentManager.findFragmentById(R.id.miniPlayerFragment) as MiniPlayerFragment
miniPlayerFragment!!.view!!.setOnClickListener { expandPanel() }
}
override fun onServiceConnected() {
super.onServiceConnected()
if (!MusicPlayerRemote.playingQueue.isEmpty()) {
slidingLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
slidingLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
hideBottomBar(false)
}
})
} // don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout
}
override fun onQueueChanged() {
super.onQueueChanged()
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
}
override fun onBackPressed() {
if (!handleBackPress())
super.onBackPressed()
}
open fun handleBackPress(): Boolean {
if (slidingLayout.panelHeight != 0 && playerFragment!!.onBackPressed())
return true
if (panelState == SlidingUpPanelLayout.PanelState.EXPANDED) {
collapsePanel()
return true
}
return false
}
override fun onPanelSlide(panel: View?, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset)
}
override fun onPanelStateChanged(panel: View, previousState: SlidingUpPanelLayout.PanelState, newState: SlidingUpPanelLayout.PanelState) {
when (newState) {
SlidingUpPanelLayout.PanelState.COLLAPSED -> onPanelCollapsed()
SlidingUpPanelLayout.PanelState.EXPANDED -> onPanelExpanded()
SlidingUpPanelLayout.PanelState.ANCHORED -> collapsePanel() // this fixes a bug where the panel would get stuck for some reason
else -> {
}
}
}
override fun onPaletteColorChanged() {
if (panelState == SlidingUpPanelLayout.PanelState.EXPANDED) {
val paletteColor = playerFragment!!.paletteColor
super.setTaskDescriptionColor(paletteColor)
val isColorLight = ColorUtil.isColorLight(paletteColor)
if (PreferenceUtil.getInstance().adaptiveColor &&
(currentNowPlayingScreen == NORMAL || currentNowPlayingScreen == FLAT)) {
super.setLightNavigationBar(true)
super.setLightStatusbar(isColorLight)
} else if (currentNowPlayingScreen == FULL || currentNowPlayingScreen == CARD ||
currentNowPlayingScreen == FIT || /*currentNowPlayingScreen == NowPlayingScreen.CLASSIC ||*/
currentNowPlayingScreen == BLUR || currentNowPlayingScreen == BLUR_CARD) {
super.setLightStatusbar(false)
super.setLightNavigationBar(true)
} else if (currentNowPlayingScreen == COLOR) {
super.setNavigationbarColor(paletteColor)
super.setLightNavigationBar(isColorLight)
super.setLightStatusbar(isColorLight)
} else {
super.setLightStatusbar(ColorUtil.isColorLight(ThemeStore.primaryColor(this)))
super.setLightNavigationBar(true)
}
}
}
override fun setLightStatusbar(enabled: Boolean) {
lightStatusBar = enabled
if (panelState == SlidingUpPanelLayout.PanelState.COLLAPSED) {
super.setLightStatusbar(enabled)
}
}
override fun setLightNavigationBar(enabled: Boolean) {
lightNavigationBar = enabled
if (panelState == SlidingUpPanelLayout.PanelState.COLLAPSED) {
super.setLightNavigationBar(enabled)
}
}
override fun setNavigationbarColor(color: Int) {
navigationBarColor = color
if (panelState == SlidingUpPanelLayout.PanelState.COLLAPSED) {
if (navigationBarColorAnimator != null) navigationBarColorAnimator!!.cancel()
super.setNavigationbarColor(color)
}
}
override fun setTaskDescriptionColor(color: Int) {
taskColor = color
if (panelState == SlidingUpPanelLayout.PanelState.COLLAPSED) {
super.setTaskDescriptionColor(color)
}
}
}

View file

@ -0,0 +1,205 @@
package code.name.monkey.retromusic.activities.base
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
import android.view.KeyEvent
import android.view.View
import android.view.WindowManager
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import code.name.monkey.appthemehelper.ATH
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.*
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
abstract class AbsThemeActivity : AbsCrashCollector(), Runnable {
private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(PreferenceUtil.getInstance().generalTheme)
hideStatusBar()
super.onCreate(savedInstanceState)
//MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this)
changeBackgroundShape()
setImmersiveFullscreen()
registerSystemUiVisibility()
toggleScreenOn()
}
private fun toggleScreenOn() {
if (PreferenceUtil.getInstance().isScreenOnEnabled) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
hideStatusBar()
handler.removeCallbacks(this)
handler.postDelayed(this, 300)
} else {
handler.removeCallbacks(this)
}
}
fun hideStatusBar() {
hideStatusBar(PreferenceUtil.getInstance().fullScreenMode)
}
private fun hideStatusBar(fullscreen: Boolean) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
statusBar.visibility = if (fullscreen) View.GONE else View.VISIBLE
}
}
private fun changeBackgroundShape() {
var background: Drawable? = if (PreferenceUtil.getInstance().isRoundCorners)
ContextCompat.getDrawable(this, R.drawable.round_window)
else
ContextCompat.getDrawable(this, R.drawable.square_window)
background = TintHelper.createTintedDrawable(background, ThemeStore.primaryColor(this))
window.setBackgroundDrawable(background)
}
fun setDrawUnderStatusBar() {
if (VersionUtils.hasLollipop()) {
RetroUtil.setAllowDrawUnderStatusBar(window)
} else if (VersionUtils.hasKitKat()) {
RetroUtil.setStatusBarTranslucent(window)
}
}
fun setDrawUnderNavigationBar() {
RetroUtil.setAllowDrawUnderNavigationBar(window)
}
/**
* This will set the color of the view with the id "status_bar" on KitKat and Lollipop. On
* Lollipop if no such view is found it will set the statusbar color using the native method.
*
* @param color the new statusbar color (will be shifted down on Lollipop and above)
*/
fun setStatusbarColor(color: Int) {
if (VersionUtils.hasKitKat()) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
when {
VersionUtils.hasMarshmallow() -> window.statusBarColor = color
VersionUtils.hasLollipop() -> statusBar.setBackgroundColor(ColorUtil.darkenColor(color))
else -> statusBar.setBackgroundColor(color)
}
} else {
when {
VersionUtils.hasMarshmallow() -> window.statusBarColor = color
else -> window.statusBarColor = ColorUtil.darkenColor(color)
}
}
}
setLightStatusbarAuto(color)
}
fun setStatusbarColorAuto() {
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
setStatusbarColor(ThemeStore.primaryColor(this))
}
open fun setTaskDescriptionColor(@ColorInt color: Int) {
ATH.setTaskDescriptionColor(this, color)
}
fun setTaskDescriptionColorAuto() {
setTaskDescriptionColor(ThemeStore.primaryColor(this))
}
open fun setNavigationbarColor(color: Int) {
if (ThemeStore.coloredNavigationBar(this)) {
ATH.setNavigationbarColor(this, color)
} else {
ATH.setNavigationbarColor(this, Color.BLACK)
}
}
fun setNavigationbarColorAuto() {
setNavigationbarColor(ThemeStore.navigationBarColor(this))
}
open fun setLightStatusbar(enabled: Boolean) {
ATH.setLightStatusbar(this, enabled)
}
fun setLightStatusbarAuto(bgColor: Int) {
setLightStatusbar(ColorUtil.isColorLight(bgColor))
}
open fun setLightNavigationBar(enabled: Boolean) {
if (!ATHUtil.isWindowBackgroundDark(this) and ThemeStore.coloredNavigationBar(this)) {
ATH.setLightNavigationbar(this, enabled)
}
}
private fun registerSystemUiVisibility() {
val decorView = window.decorView
decorView.setOnSystemUiVisibilityChangeListener { visibility ->
if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
setImmersiveFullscreen()
}
}
}
private fun unregisterSystemUiVisibility() {
val decorView = window.decorView
decorView.setOnSystemUiVisibilityChangeListener(null)
}
private fun setImmersiveFullscreen() {
val flags = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
if (PreferenceUtil.getInstance().fullScreenMode) {
window.decorView.systemUiVisibility = flags
}
}
private fun exitFullscreen() {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
}
override fun run() {
setImmersiveFullscreen()
}
override fun onStop() {
handler.removeCallbacks(this)
super.onStop()
}
public override fun onDestroy() {
super.onDestroy()
unregisterSystemUiVisibility()
exitFullscreen()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
handler.removeCallbacks(this)
handler.postDelayed(this, 500)
}
return super.onKeyDown(keyCode, event)
}
}

View file

@ -0,0 +1,315 @@
package code.name.monkey.retromusic.activities.bugreport
import android.app.Activity
import android.app.Dialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.view.MenuItem
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.annotation.StringDef
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.misc.DialogAsyncTask
import code.name.monkey.retromusic.activities.base.AbsThemeActivity
import code.name.monkey.retromusic.activities.bugreport.model.DeviceInfo
import code.name.monkey.retromusic.activities.bugreport.model.Report
import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo
import code.name.monkey.retromusic.activities.bugreport.model.github.GithubLogin
import code.name.monkey.retromusic.activities.bugreport.model.github.GithubTarget
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.callbacks.onCancel
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.activity_bug_report.*
import kotlinx.android.synthetic.main.bug_report_card_device_info.*
import kotlinx.android.synthetic.main.bug_report_card_report.*
import org.eclipse.egit.github.core.Issue
import org.eclipse.egit.github.core.client.GitHubClient
import org.eclipse.egit.github.core.client.RequestException
import org.eclipse.egit.github.core.service.IssueService
import java.io.IOException
private const val RESULT_SUCCESS = "RESULT_OK"
private const val RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS"
private const val RESULT_INVALID_TOKEN = "RESULT_INVALID_TOKEN"
private const val RESULT_ISSUES_NOT_ENABLED = "RESULT_ISSUES_NOT_ENABLED"
private const val RESULT_UNKNOWN = "RESULT_UNKNOWN"
@StringDef(RESULT_SUCCESS, RESULT_BAD_CREDENTIALS, RESULT_INVALID_TOKEN, RESULT_ISSUES_NOT_ENABLED, RESULT_UNKNOWN)
@Retention(AnnotationRetention.SOURCE)
private annotation class Result
open class BugReportActivity : AbsThemeActivity() {
private var deviceInfo: DeviceInfo? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bug_report)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
initViews()
if (TextUtils.isEmpty(title))
setTitle(R.string.report_an_issue)
deviceInfo = DeviceInfo(this)
airTextDeviceInfo!!.text = deviceInfo!!.toString()
}
private fun initViews() {
val accentColor = ThemeStore.accentColor(this)
val primaryColor = ThemeStore.primaryColor(this)
toolbar!!.setBackgroundColor(primaryColor)
setSupportActionBar(toolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
TintHelper.setTintAuto(optionUseAccount!!, accentColor, false)
optionUseAccount!!.setOnClickListener {
inputTitle!!.isEnabled = true
inputDescription!!.isEnabled = true
inputUsername!!.isEnabled = true
inputPassword!!.isEnabled = true
optionAnonymous!!.isChecked = false
sendFab!!.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab!!.setImageResource(R.drawable.ic_send_white_24dp)
sendFab!!.show()
}
})
}
TintHelper.setTintAuto(optionAnonymous!!, accentColor, false)
optionAnonymous!!.setOnClickListener {
inputTitle!!.isEnabled = false
inputDescription!!.isEnabled = false
inputUsername!!.isEnabled = false
inputPassword!!.isEnabled = false
optionUseAccount!!.isChecked = false
sendFab!!.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
sendFab!!.setImageResource(R.drawable.ic_open_in_browser_white_24dp)
sendFab!!.show()
}
})
}
inputPassword!!.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEND) {
reportIssue()
return@setOnEditorActionListener true
}
false
}
airTextDeviceInfo!!.setOnClickListener { copyDeviceInfoToClipBoard() }
TintHelper.setTintAuto(sendFab!!, accentColor, true)
sendFab!!.setOnClickListener { reportIssue() }
TintHelper.setTintAuto(inputTitle!!, accentColor, false)
TintHelper.setTintAuto(inputDescription!!, accentColor, false)
TintHelper.setTintAuto(inputUsername!!, accentColor, false)
TintHelper.setTintAuto(inputPassword!!, accentColor, false)
}
private fun reportIssue() {
if (optionUseAccount!!.isChecked) {
if (!validateInput()) return
val username = inputUsername!!.text!!.toString()
val password = inputPassword!!.text!!.toString()
sendBugReport(GithubLogin(username, password))
} else {
copyDeviceInfoToClipBoard()
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(ISSUE_TRACKER_LINK)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
}
}
private fun copyDeviceInfoToClipBoard() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo!!.toMarkdown())
clipboard.primaryClip = clip
Toast.makeText(this@BugReportActivity, R.string.copied_device_info_to_clipboard, Toast.LENGTH_LONG).show()
}
private fun validateInput(): Boolean {
var hasErrors = false
if (optionUseAccount!!.isChecked) {
if (TextUtils.isEmpty(inputUsername!!.text)) {
setError(inputLayoutUsername!!, R.string.bug_report_no_username)
hasErrors = true
} else {
removeError(inputLayoutUsername!!)
}
if (TextUtils.isEmpty(inputPassword!!.text)) {
setError(inputLayoutPassword!!, R.string.bug_report_no_password)
hasErrors = true
} else {
removeError(inputLayoutPassword!!)
}
}
if (TextUtils.isEmpty(inputTitle!!.text)) {
setError(inputLayoutTitle!!, R.string.bug_report_no_title)
hasErrors = true
} else {
removeError(inputLayoutTitle!!)
}
if (TextUtils.isEmpty(inputDescription!!.text)) {
setError(inputLayoutDescription!!, R.string.bug_report_no_description)
hasErrors = true
} else {
removeError(inputLayoutDescription!!)
}
return !hasErrors
}
private fun setError(editTextLayout: TextInputLayout, @StringRes errorRes: Int) {
editTextLayout.error = getString(errorRes)
}
private fun removeError(editTextLayout: TextInputLayout) {
editTextLayout.error = null
}
private fun sendBugReport(login: GithubLogin) {
if (!validateInput()) return
val bugTitle = inputTitle!!.text!!.toString()
val bugDescription = inputDescription!!.text!!.toString()
val extraInfo = ExtraInfo()
onSaveExtraInfo()
val report = Report(bugTitle, bugDescription, deviceInfo, extraInfo)
val target = GithubTarget("h4h13", "RetroMusicPlayer")
ReportIssueAsyncTask.report(this, report, target, login)
}
private fun onSaveExtraInfo() {}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
private class ReportIssueAsyncTask private constructor(activity: Activity, private val report: Report, private val target: GithubTarget,
private val login: GithubLogin) : DialogAsyncTask<Void, Void, String>(activity) {
override fun createDialog(context: Context): Dialog {
return AlertDialog.Builder(context)
.show()
}
@Result
override fun doInBackground(vararg params: Void): String {
val client: GitHubClient = if (login.shouldUseApiToken()) {
GitHubClient().setOAuth2Token(login.apiToken)
} else {
GitHubClient().setCredentials(login.username, login.password)
}
val issue = Issue().setTitle(report.title).setBody(report.description)
try {
IssueService(client).createIssue(target.username, target.repository, issue)
return RESULT_SUCCESS
} catch (e: RequestException) {
return when (e.status) {
STATUS_BAD_CREDENTIALS -> {
if (login.shouldUseApiToken()) RESULT_INVALID_TOKEN else RESULT_BAD_CREDENTIALS
}
STATUS_ISSUES_NOT_ENABLED -> RESULT_ISSUES_NOT_ENABLED
else -> {
e.printStackTrace()
RESULT_UNKNOWN
}
}
} catch (e: IOException) {
e.printStackTrace()
return RESULT_UNKNOWN
}
}
override fun onPostExecute(@Result result: String) {
super.onPostExecute(result)
val context = context ?: return
when (result) {
RESULT_SUCCESS -> tryToFinishActivity()
RESULT_BAD_CREDENTIALS -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_wrong_credentials)
positiveButton(android.R.string.ok)
}
RESULT_INVALID_TOKEN -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_invalid_token)
positiveButton(android.R.string.ok)
}
RESULT_ISSUES_NOT_ENABLED -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_issues_not_available)
positiveButton(android.R.string.ok)
}
else -> MaterialDialog(context).show {
title(R.string.bug_report_failed)
message(R.string.bug_report_failed_unknown)
positiveButton(android.R.string.ok) { tryToFinishActivity() }
onCancel { tryToFinishActivity() }
}
}
}
private fun tryToFinishActivity() {
val context = context
if (context is Activity && !context.isFinishing) {
context.finish()
}
}
companion object {
fun report(activity: Activity, report: Report, target: GithubTarget, login: GithubLogin) {
ReportIssueAsyncTask(activity, report, target, login).execute()
}
}
}
companion object {
private const val STATUS_BAD_CREDENTIALS = 401
private const val STATUS_ISSUES_NOT_ENABLED = 410
private const val ISSUE_TRACKER_LINK = "https://github.com/h4h13/RetroMusicPlayer"
}
}

View file

@ -0,0 +1 @@
package code.name.monkey.retromusic.activities.bugreport import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.bugreport.model.DeviceInfo import kotlinx.android.synthetic.main.activity_error_handler.* class ErrorHandlerActivity : AppCompatActivity() { private var deviceInfo: DeviceInfo? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_error_handler) deviceInfo = DeviceInfo(this) clearAppData.setOnClickListener { try { val intent = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS) intent.data = Uri.parse("package:$packageName") startActivity(intent) } catch (e: ActivityNotFoundException) { val intent = Intent(android.provider.Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS) startActivity(intent) } } sendCrashLog.setOnClickListener { val sendIntent = Intent(Intent.ACTION_SEND) val subject = "Error report" val body = intent.getStringExtra("error") + "\n" + deviceInfo!!.toString() sendIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf("monkeycodeapp@gmail.com")) sendIntent.putExtra(Intent.EXTRA_TEXT, body) sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject) sendIntent.type = "message/rfc822" startActivity(Intent.createChooser(sendIntent, "Send crash log")) deleteFile("stack.trace") } showCrashError.text = String.format("%s", intent.getStringExtra("error")) showCrashError.append(deviceInfo!!.toString()) } }

View file

@ -0,0 +1,105 @@
package code.name.monkey.retromusic.activities.bugreport.model;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import java.util.Arrays;
import androidx.annotation.IntRange;
import code.name.monkey.retromusic.util.PreferenceUtil;
public class DeviceInfo {
private final int versionCode;
private final String versionName;
private final String buildVersion = Build.VERSION.INCREMENTAL;
private final String releaseVersion = Build.VERSION.RELEASE;
@IntRange(from = 0)
private final int sdkVersion = Build.VERSION.SDK_INT;
private final String buildID = Build.DISPLAY;
private final String brand = Build.BRAND;
private final String manufacturer = Build.MANUFACTURER;
private final String device = Build.DEVICE;
private final String model = Build.MODEL;
private final String product = Build.PRODUCT;
private final String hardware = Build.HARDWARE;
private final String baseTheme;
private final String nowPlayingTheme;
private final boolean isAdaptive;
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
private final String[] abis = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
Build.SUPPORTED_ABIS : new String[]{Build.CPU_ABI, Build.CPU_ABI2};
@SuppressLint("NewApi")
private final String[] abis32Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
Build.SUPPORTED_32_BIT_ABIS : null;
@SuppressLint("NewApi")
private final String[] abis64Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
Build.SUPPORTED_64_BIT_ABIS : null;
public DeviceInfo(Context context) {
PackageInfo packageInfo;
try {
packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
packageInfo = null;
}
if (packageInfo != null) {
versionCode = packageInfo.versionCode;
versionName = packageInfo.versionName;
} else {
versionCode = -1;
versionName = null;
}
baseTheme = PreferenceUtil.getInstance().getBaseTheme();
nowPlayingTheme = context.getString(PreferenceUtil.getInstance().getNowPlayingScreen().getTitleRes());
isAdaptive = PreferenceUtil.getInstance().getAdaptiveColor();
}
public String toMarkdown() {
return "Device info:\n"
+ "---\n"
+ "<table>\n"
+ "<tr><td>App version</td><td>" + versionName + "</td></tr>\n"
+ "<tr><td>App version code</td><td>" + versionCode + "</td></tr>\n"
+ "<tr><td>Android build version</td><td>" + buildVersion + "</td></tr>\n"
+ "<tr><td>Android release version</td><td>" + releaseVersion + "</td></tr>\n"
+ "<tr><td>Android SDK version</td><td>" + sdkVersion + "</td></tr>\n"
+ "<tr><td>Android build ID</td><td>" + buildID + "</td></tr>\n"
+ "<tr><td>Device brand</td><td>" + brand + "</td></tr>\n"
+ "<tr><td>Device manufacturer</td><td>" + manufacturer + "</td></tr>\n"
+ "<tr><td>Device name</td><td>" + device + "</td></tr>\n"
+ "<tr><td>Device model</td><td>" + model + "</td></tr>\n"
+ "<tr><td>Device product name</td><td>" + product + "</td></tr>\n"
+ "<tr><td>Device hardware name</td><td>" + hardware + "</td></tr>\n"
+ "<tr><td>ABIs</td><td>" + Arrays.toString(abis) + "</td></tr>\n"
+ "<tr><td>ABIs (32bit)</td><td>" + Arrays.toString(abis32Bits) + "</td></tr>\n"
+ "<tr><td>ABIs (64bit)</td><td>" + Arrays.toString(abis64Bits) + "</td></tr>\n"
+ "</table>\n";
}
@Override
public String toString() {
return "App version: " + versionName + "\n"
+ "App version code: " + versionCode + "\n"
+ "Android build version: " + buildVersion + "\n"
+ "Android release version: " + releaseVersion + "\n"
+ "Android SDK version: " + sdkVersion + "\n"
+ "Android build ID: " + buildID + "\n"
+ "Device brand: " + brand + "\n"
+ "Device manufacturer: " + manufacturer + "\n"
+ "Device name: " + device + "\n"
+ "Device model: " + model + "\n"
+ "Device product name: " + product + "\n"
+ "Device hardware name: " + hardware + "\n"
+ "ABIs: " + Arrays.toString(abis) + "\n"
+ "ABIs (32bit): " + Arrays.toString(abis32Bits) + "\n"
+ "ABIs (64bit): " + Arrays.toString(abis64Bits) + "\n"
+ "Base theme: " + baseTheme + "\n"
+ "Now playing theme: " + nowPlayingTheme + "\n"
+ "Adaptive: " + isAdaptive;
}
}

View file

@ -0,0 +1,32 @@
package code.name.monkey.retromusic.activities.bugreport.model;
import code.name.monkey.retromusic.activities.bugreport.model.github.ExtraInfo;
public class Report {
private final String title;
private final String description;
private final DeviceInfo deviceInfo;
private final ExtraInfo extraInfo;
public Report(String title, String description, DeviceInfo deviceInfo, ExtraInfo extraInfo) {
this.title = title;
this.description = description;
this.deviceInfo = deviceInfo;
this.extraInfo = extraInfo;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description + "\n\n"
+ "-\n\n"
+ deviceInfo.toMarkdown() + "\n\n"
+ extraInfo.toMarkdown();
}
}

View file

@ -0,0 +1,59 @@
package code.name.monkey.retromusic.activities.bugreport.model.github;
import java.util.LinkedHashMap;
import java.util.Map;
public class ExtraInfo {
private final Map<String, String> extraInfo = new LinkedHashMap<>();
public void put(String key, String value) {
extraInfo.put(key, value);
}
public void put(String key, boolean value) {
extraInfo.put(key, Boolean.toString(value));
}
public void put(String key, double value) {
extraInfo.put(key, Double.toString(value));
}
public void put(String key, float value) {
extraInfo.put(key, Float.toString(value));
}
public void put(String key, long value) {
extraInfo.put(key, Long.toString(value));
}
public void put(String key, int value) {
extraInfo.put(key, Integer.toString(value));
}
public void put(String key, Object value) {
extraInfo.put(key, String.valueOf(value));
}
public void remove(String key) {
extraInfo.remove(key);
}
public String toMarkdown() {
if (extraInfo.isEmpty()) return "";
StringBuilder output = new StringBuilder();
output.append("Extra info:\n"
+ "---\n"
+ "<table>\n");
for (String key : extraInfo.keySet()) {
output.append("<tr><td>")
.append(key)
.append("</td><td>")
.append(extraInfo.get(key))
.append("</td></tr>\n");
}
output.append("</table>\n");
return output.toString();
}
}

View file

@ -0,0 +1,40 @@
package code.name.monkey.retromusic.activities.bugreport.model.github;
import android.text.TextUtils;
public class GithubLogin {
private final String username;
private final String password;
private final String apiToken;
public GithubLogin(String username, String password) {
this.username = username;
this.password = password;
this.apiToken = null;
}
public GithubLogin(String apiToken) {
this.username = null;
this.password = null;
this.apiToken = apiToken;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public boolean shouldUseApiToken() {
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password);
}
public String getApiToken() {
return apiToken;
}
}

View file

@ -0,0 +1,20 @@
package code.name.monkey.retromusic.activities.bugreport.model.github;
public class GithubTarget {
private final String username;
private final String repository;
public GithubTarget(String username, String repository) {
this.username = username;
this.repository = repository;
}
public String getUsername() {
return username;
}
public String getRepository() {
return repository;
}
}

View file

@ -0,0 +1,350 @@
package code.name.monkey.retromusic.activities.tageditor
import android.app.Activity
import android.app.SearchManager
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.view.animation.OvershootInterpolator
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.audio.AudioFile
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
import java.io.File
abstract class AbsTagEditorActivity : AbsBaseActivity() {
protected var id: Int = 0
private set
private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false
private var songPaths: List<String>? = null
lateinit var saveFab: ExtendedFloatingActionButton
protected val show: MaterialDialog
get() = MaterialDialog(this@AbsTagEditorActivity).show {
title(R.string.update_image)
listItems(items = items) { _, position, _ ->
when (position) {
0 -> getImageFromLastFM()
1 -> startImagePicker()
2 -> searchImageOnWeb()
3 -> deleteImage()
}
}
}
protected abstract val contentViewLayout: Int
internal val albumArtist: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault
.getFirst(FieldKey.ALBUM_ARTIST)
} catch (ignored: Exception) {
null
}
}
protected val songTitle: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TITLE)
} catch (ignored: Exception) {
null
}
}
protected val composer: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.COMPOSER)
} catch (ignored: Exception) {
null
}
}
protected val albumTitle: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ALBUM)
} catch (ignored: Exception) {
null
}
}
protected val artistName: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.ARTIST)
} catch (ignored: Exception) {
null
}
}
protected val albumArtistName: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault
.getFirst(FieldKey.ALBUM_ARTIST)
} catch (ignored: Exception) {
null
}
}
protected val genreName: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.GENRE)
} catch (ignored: Exception) {
null
}
}
protected val songYear: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.YEAR)
} catch (ignored: Exception) {
null
}
}
protected val trackNumber: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.TRACK)
} catch (ignored: Exception) {
null
}
}
protected val lyrics: String?
get() {
return try {
getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault.getFirst(FieldKey.LYRICS)
} catch (ignored: Exception) {
null
}
}
protected val albumArt: Bitmap?
get() {
try {
val artworkTag = getAudioFile(songPaths!![0]).tagOrCreateAndSetDefault
.firstArtwork
if (artworkTag != null) {
val artworkBinaryData = artworkTag.binaryData
return BitmapFactory.decodeByteArray(artworkBinaryData, 0, artworkBinaryData.size)
}
return null
} catch (ignored: Exception) {
return null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentViewLayout)
saveFab = findViewById(R.id.saveTags)
getIntentExtras()
songPaths = getSongPaths()
if (songPaths!!.isEmpty()) {
finish()
return
}
setUpViews()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
}
private fun setUpViews() {
setUpScrollView()
setUpFab()
setUpImageView()
}
private fun setUpScrollView() {
//observableScrollView.setScrollViewCallbacks(observableScrollViewCallbacks);
}
private lateinit var items: List<String>
private fun setUpImageView() {
loadCurrentImage()
items = listOf(getString(R.string.download_from_last_fm), getString(R.string.pick_from_local_storage), getString(R.string.web_search), getString(R.string.remove_cover))
editorImage.setOnClickListener { show }
}
private fun startImagePicker() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
startActivityForResult(Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), REQUEST_CODE_SELECT_IMAGE)
}
protected abstract fun loadCurrentImage()
protected abstract fun getImageFromLastFM()
protected abstract fun searchImageOnWeb()
protected abstract fun deleteImage()
private fun setUpFab() {
saveFab.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.accentColor(this)))).apply {
saveFab.setTextColor(this)
saveFab.iconTint = this
}
saveFab.apply {
scaleX = 0f
scaleY = 0f
isEnabled = false
setOnClickListener { save() }
TintHelper.setTintAuto(this, ThemeStore.accentColor(this@AbsTagEditorActivity), true)
}
}
protected abstract fun save()
private fun getIntentExtras() {
val intentExtras = intent.extras
if (intentExtras != null) {
id = intentExtras.getInt(EXTRA_ID)
}
}
protected abstract fun getSongPaths(): List<String>
protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder()
for (key in keys) {
stringBuilder.append(key)
stringBuilder.append(" ")
}
val intent = Intent(Intent.ACTION_WEB_SEARCH)
intent.putExtra(SearchManager.QUERY, stringBuilder.toString())
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when (id) {
android.R.id.home -> {
super.onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
protected fun setNoImageMode() {
isInNoImageMode = true
imageContainer!!.visibility = View.GONE
editorImage.visibility = View.GONE
editorImage.isEnabled = false
setColors(intent.getIntExtra(EXTRA_PALETTE, ThemeStore.primaryColor(this)))
}
protected fun dataChanged() {
showFab()
}
private fun showFab() {
saveFab.animate()
.setDuration(500)
.setInterpolator(OvershootInterpolator())
.scaleX(1f)
.scaleY(1f)
.start()
saveFab.isEnabled = true
}
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
if (bitmap == null) {
editorImage.setImageResource(R.drawable.default_album_art)
} else {
editorImage.setImageBitmap(bitmap)
}
setColors(bgColor)
}
protected open fun setColors(color: Int) {
paletteColorPrimary = color
}
protected fun writeValuesToFiles(fieldKeyValueMap: Map<FieldKey, String>,
artworkInfo: ArtworkInfo?) {
RetroUtil.hideSoftKeyboard(this)
WriteTagsAsyncTask(this)
.execute(WriteTagsAsyncTask.LoadingInfo(getSongPaths(), fieldKeyValueMap, artworkInfo))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
val selectedImage = data!!.data
loadImageFromFile(selectedImage)
}
}
}
protected abstract fun loadImageFromFile(selectedFile: Uri?)
private fun getAudioFile(path: String): AudioFile {
return try {
AudioFileIO.read(File(path))
} catch (e: Exception) {
Log.e(TAG, "Could not read audio file $path", e)
AudioFile()
}
}
class ArtworkInfo constructor(val albumId: Int, val artwork: Bitmap?)
companion object {
const val EXTRA_ID = "extra_id"
const val EXTRA_PALETTE = "extra_palette"
private val TAG = AbsTagEditorActivity::class.java.simpleName
private const val REQUEST_CODE_SELECT_IMAGE = 1000
}
}

View file

@ -0,0 +1,228 @@
package code.name.monkey.retromusic.activities.tageditor
import android.app.Activity
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.widget.Toast
import androidx.core.content.ContextCompat
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroSimpleTarget
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.loaders.AlbumLoader
import code.name.monkey.retromusic.rest.LastFMRestClient
import code.name.monkey.retromusic.rest.model.LastFmAlbum
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.LastFMUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
import com.bumptech.glide.GenericTransitionOptions
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.transition.Transition
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.tag.FieldKey
import java.util.*
class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val contentViewLayout: Int
get() = R.layout.activity_album_tag_editor
override fun loadImageFromFile(selectedFile: Uri?) {
GlideApp.with(this).`as`(BitmapPaletteWrapper::class.java)
.load(selectedFile)
.transition(GenericTransitionOptions<BitmapPaletteWrapper>().transition(android.R.anim.fade_in))
.apply(RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true))
.into(object : RetroSimpleTarget<BitmapPaletteWrapper>() {
override fun onResourceReady(resource: BitmapPaletteWrapper, transition: Transition<in BitmapPaletteWrapper>?) {
RetroColorUtil.getColor(resource.palette, Color.TRANSPARENT)
albumArtBitmap = ImageUtil.resizeBitmap(resource.bitmap, 2048)
setImageBitmap(albumArtBitmap, RetroColorUtil.getColor(resource.palette, ATHUtil.resolveColor(this@AlbumTagEditorActivity, R.attr.defaultFooterColor)))
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
})
}
private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false
private var lastFMRestClient: LastFMRestClient? = null
private val disposable = CompositeDisposable()
private fun setupToolbar() {
bannerTitle.setTextColor(Color.WHITE)
toolbar.setNavigationOnClickListener { onBackPressed() }
ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(this, toolbar, Color.TRANSPARENT)
title = null
setSupportActionBar(toolbar)
TintHelper.setTintAuto(content, ThemeStore.primaryColor(this), true)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
lastFMRestClient = LastFMRestClient(this)
setUpViews()
setupToolbar()
}
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(yearContainer, false)
MaterialUtil.setTint(genreContainer, false)
MaterialUtil.setTint(albumTitleContainer, false)
MaterialUtil.setTint(albumArtistContainer, false)
albumText.addTextChangedListener(this)
albumArtistText.addTextChangedListener(this)
genreTitle.addTextChangedListener(this)
yearTitle.addTextChangedListener(this)
}
private fun fillViewsWithFileTags() {
albumText.setText(albumTitle)
albumArtistText.setText(albumArtistName)
genreTitle.setText(genreName)
yearTitle.setText(songYear)
}
override fun loadCurrentImage() {
val bitmap = albumArt
setImageBitmap(bitmap, getColor(generatePalette(bitmap), ATHUtil.resolveColor(this, R.attr.defaultFooterColor)))
deleteAlbumArt = false
}
override fun getImageFromLastFM() {
val albumTitleStr = albumText.text.toString()
val albumArtistNameStr = albumArtistText.text.toString()
if (albumArtistNameStr.trim { it <= ' ' } == "" || albumTitleStr.trim { it <= ' ' } == "") {
Toast.makeText(this, resources.getString(R.string.album_or_artist_empty), Toast.LENGTH_SHORT).show()
return
}
disposable.add(lastFMRestClient!!.apiService
.getAlbumInfo(albumTitleStr, albumArtistNameStr, null)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation())
.subscribe { this.extractDetails(it) })
}
override fun onPause() {
super.onPause()
disposable.clear()
}
private fun extractDetails(lastFmAlbum: LastFmAlbum) {
if (lastFmAlbum.album != null) {
val url = LastFMUtil.getLargestAlbumImageUrl(lastFmAlbum.album.image)
if (!TextUtils.isEmpty(url) && url.trim { it <= ' ' }.isNotEmpty()) {
GlideApp.with(this)
.`as`(BitmapPaletteWrapper::class.java)
.load(url)
.apply(RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.error(R.drawable.default_album_art))
.into(object : RetroSimpleTarget<BitmapPaletteWrapper>() {
override fun onResourceReady(resource: BitmapPaletteWrapper, transition: Transition<in BitmapPaletteWrapper>?) {
albumArtBitmap = ImageUtil.resizeBitmap(resource.bitmap, 2048)
setImageBitmap(albumArtBitmap, getColor(resource.palette,
ContextCompat.getColor(this@AlbumTagEditorActivity, R.color.md_grey_500)))
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
})
return
}
if (lastFmAlbum.album.tags.tag.size > 0) {
genreTitle.setText(lastFmAlbum.album.tags.tag[0].name)
}
}
toastLoadingFailed()
}
private fun toastLoadingFailed() {
Toast.makeText(this@AlbumTagEditorActivity, R.string.could_not_download_album_cover, Toast.LENGTH_SHORT).show()
}
override fun searchImageOnWeb() {
searchWebFor(albumText.text.toString(), albumArtistText.text.toString())
}
override fun deleteImage() {
setImageBitmap(BitmapFactory.decodeResource(resources, R.drawable.default_album_art), ATHUtil.resolveColor(this, R.attr.defaultFooterColor))
deleteAlbumArt = true
dataChanged()
}
override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString()
//android seems not to recognize album_artist field so we additionally write the normal artist field
fieldKeyValueMap[FieldKey.ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreTitle.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearTitle.text.toString()
writeValuesToFiles(fieldKeyValueMap, if (deleteAlbumArt) AbsTagEditorActivity.ArtworkInfo(id, null)
else if (albumArtBitmap == null) null else AbsTagEditorActivity.ArtworkInfo(id, albumArtBitmap!!))
}
override fun getSongPaths(): List<String> {
val songs = AlbumLoader.getAlbum(this, id).blockingFirst().songs
val paths = ArrayList<String>(songs!!.size)
for (song in songs) {
paths.add(song.data!!)
}
return paths
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
dataChanged()
}
override fun setColors(color: Int) {
super.setColors(color)
saveFab.backgroundTintList = ColorStateList.valueOf(color)
}
companion object {
val TAG: String = AlbumTagEditorActivity::class.java.simpleName
}
}

View file

@ -0,0 +1,135 @@
package code.name.monkey.retromusic.activities.tageditor
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import androidx.core.content.ContextCompat
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.loaders.SongLoader
import kotlinx.android.synthetic.main.activity_song_tag_editor.*
import org.jaudiotagger.tag.FieldKey
import java.util.*
class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val contentViewLayout: Int
get() = R.layout.activity_song_tag_editor
private fun setupToolbar() {
appBarLayout.setBackgroundColor(ThemeStore.primaryColor(this))
toolbar.apply {
setBackgroundColor(ThemeStore.primaryColor(context))
navigationIcon = TintHelper.createTintedDrawable(ContextCompat.getDrawable(context, R.drawable.ic_keyboard_backspace_black_24dp), ThemeStore.textColorPrimary(context))
setNavigationOnClickListener { onBackPressed() }
setSupportActionBar(toolbar)
}
title = null
bannerTitle.setTextColor(ThemeStore.textColorPrimary(this))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setNoImageMode()
setUpViews()
setupToolbar()
setStatusbarColorAuto()
}
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(songTextContainer,false)
MaterialUtil.setTint(composerContainer, false)
MaterialUtil.setTint(albumTextContainer, false)
MaterialUtil.setTint(artistContainer, false)
MaterialUtil.setTint(albumArtistContainer, false)
MaterialUtil.setTint(yearContainer, false)
MaterialUtil.setTint(genreContainer, false)
MaterialUtil.setTint(trackNumberContainer, false)
MaterialUtil.setTint(lyricsContainer, false)
albumText.addTextChangedListener(this)
songText.addTextChangedListener(this)
albumText.addTextChangedListener(this)
artistText.addTextChangedListener(this)
genreText.addTextChangedListener(this)
yearText.addTextChangedListener(this)
trackNumberText.addTextChangedListener(this)
lyricsText.addTextChangedListener(this)
songComposerText.addTextChangedListener(this)
}
private fun fillViewsWithFileTags() {
songText.setText(songTitle)
albumArtistText.setText(albumArtist)
albumText.setText(albumTitle)
artistText.setText(artistName)
genreText.setText(genreName)
yearText.setText(songYear)
trackNumberText.setText(trackNumber)
lyricsText.setText(lyrics)
songComposerText.setText(composer)
}
override fun loadCurrentImage() {
}
override fun getImageFromLastFM() {
}
override fun searchImageOnWeb() {
}
override fun deleteImage() {
}
override fun save() {
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.TITLE] = songText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM] = albumText.text.toString()
fieldKeyValueMap[FieldKey.ARTIST] = artistText.text.toString()
fieldKeyValueMap[FieldKey.GENRE] = genreText.text.toString()
fieldKeyValueMap[FieldKey.YEAR] = yearText.text.toString()
fieldKeyValueMap[FieldKey.TRACK] = trackNumberText.text.toString()
fieldKeyValueMap[FieldKey.LYRICS] = lyricsText.text.toString()
fieldKeyValueMap[FieldKey.ALBUM_ARTIST] = albumArtistText.text.toString()
fieldKeyValueMap[FieldKey.COMPOSER] = songComposerText.text.toString()
writeValuesToFiles(fieldKeyValueMap, null)
}
override fun getSongPaths(): List<String> {
val paths = ArrayList<String>(1)
paths.add(SongLoader.getSong(this, id).blockingFirst().data)
return paths
}
override fun loadImageFromFile(selectedFile: Uri?) {
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
dataChanged()
}
companion object {
val TAG: String = SongTagEditorActivity::class.java.simpleName
}
}

View file

@ -0,0 +1,165 @@
package code.name.monkey.retromusic.activities.tageditor;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaScannerConnection;
import com.afollestad.materialdialogs.MaterialDialog;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
import org.jaudiotagger.tag.images.ArtworkFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
import code.name.monkey.retromusic.util.MusicUtil;
public class WriteTagsAsyncTask extends
DialogAsyncTask<WriteTagsAsyncTask.LoadingInfo, Integer, String[]> {
private Context applicationContext;
public WriteTagsAsyncTask(Context context) {
super(context);
applicationContext = context;
}
@Override
protected String[] doInBackground(LoadingInfo... params) {
try {
LoadingInfo info = params[0];
Artwork artwork = null;
File albumArtFile = null;
if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) {
try {
albumArtFile = MusicUtil.createAlbumArtFile().getCanonicalFile();
info.artworkInfo.getArtwork()
.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
} catch (IOException e) {
e.printStackTrace();
}
}
int counter = 0;
boolean wroteArtwork = false;
boolean deletedArtwork = false;
for (String filePath : info.filePaths) {
publishProgress(++counter, info.filePaths.size());
try {
AudioFile audioFile = AudioFileIO.read(new File(filePath));
Tag tag = audioFile.getTagOrCreateAndSetDefault();
if (info.fieldKeyValueMap != null) {
for (Map.Entry<FieldKey, String> entry : info.fieldKeyValueMap.entrySet()) {
try {
tag.setField(entry.getKey(), entry.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.getArtwork() == null) {
tag.deleteArtworkField();
deletedArtwork = true;
} else if (artwork != null) {
tag.deleteArtworkField();
tag.setField(artwork);
wroteArtwork = true;
}
}
audioFile.commit();
} catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
}
Context context = getContext();
if (context != null) {
if (wroteArtwork) {
MusicUtil.insertAlbumArt(context, info.artworkInfo.getAlbumId(), albumArtFile.getPath());
} else if (deletedArtwork) {
MusicUtil.deleteAlbumArt(context, info.artworkInfo.getAlbumId());
}
}
return info.filePaths.toArray(new String[info.filePaths.size()]);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(String[] toBeScanned) {
super.onPostExecute(toBeScanned);
scan(toBeScanned);
}
@Override
protected void onCancelled(String[] toBeScanned) {
super.onCancelled(toBeScanned);
scan(toBeScanned);
}
private void scan(String[] toBeScanned) {
Context context = getContext();
MediaScannerConnection.scanFile(applicationContext, toBeScanned, null,
context instanceof Activity ? new UpdateToastMediaScannerCompletionListener(
(Activity) context, toBeScanned) : null);
}
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialDialog(context)
.title(R.string.saving_changes, "")
.cancelable(false);
}
@Override
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
super.onProgressUpdate(dialog, values);
//((MaterialDialog) dialog).setMaxProgress(values[1]);
//((MaterialDialog) dialog).setProgress(values[0]);
}
public static class LoadingInfo {
final Collection<String> filePaths;
@Nullable
final Map<FieldKey, String> fieldKeyValueMap;
@Nullable
private AbsTagEditorActivity.ArtworkInfo artworkInfo;
public LoadingInfo(Collection<String> filePaths,
@Nullable Map<FieldKey, String> fieldKeyValueMap,
@Nullable AbsTagEditorActivity.ArtworkInfo artworkInfo) {
this.filePaths = filePaths;
this.fieldKeyValueMap = fieldKeyValueMap;
this.artworkInfo = artworkInfo;
}
}
}