My initial commit

Removed Google play dependencies
This commit is contained in:
Muntashir Al-Islam 2020-06-17 22:50:30 +06:00
parent fd582fff69
commit 301ac10570
430 changed files with 2210 additions and 3137 deletions

View file

@ -1,94 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic
import android.widget.Toast
import androidx.multidex.MultiDexApplication
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.dagger.DaggerMusicComponent
import code.name.monkey.retromusic.dagger.MusicComponent
import code.name.monkey.retromusic.dagger.module.AppModule
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails
class App : MultiDexApplication() {
lateinit var billingProcessor: BillingProcessor
override fun onCreate() {
super.onCreate()
instance = this
musicComponent = initDagger(this)
// default theme
if (!ThemeStore.isConfigured(this, 3)) {
ThemeStore.editTheme(this)
.accentColorRes(R.color.md_deep_purple_A200)
.coloredNavigationBar(true)
.commit()
}
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).initDynamicShortcuts()
// automatically restores purchases
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY,
object : BillingProcessor.IBillingHandler {
override fun onProductPurchased(productId: String, details: TransactionDetails?) {}
override fun onPurchaseHistoryRestored() {
Toast.makeText(
this@App,
R.string.restored_previous_purchase_please_restart,
Toast.LENGTH_LONG
).show()
}
override fun onBillingError(errorCode: Int, error: Throwable?) {}
override fun onBillingInitialized() {}
})
}
private fun initDagger(app: App): MusicComponent =
DaggerMusicComponent.builder()
.appModule(AppModule(app))
.build()
override fun onTerminate() {
super.onTerminate()
billingProcessor.release()
}
companion object {
private var instance: App? = null
fun getContext(): App {
return instance!!
}
fun isProVersion(): Boolean {
return BuildConfig.DEBUG || instance?.billingProcessor!!.isPurchased(
PRO_VERSION_PRODUCT_ID
)
}
lateinit var musicComponent: MusicComponent
const val PRO_VERSION_PRODUCT_ID = "pro_version"
}
}

View file

@ -1,127 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic
import android.provider.BaseColumns
import android.provider.MediaStore
object Constants {
const val RATE_ON_GOOGLE_PLAY =
"https://play.google.com/store/apps/details?id=code.name.monkey.retromusic"
const val TRANSLATE = "http://monkeycodeapp.oneskyapp.com/collaboration/project?id=238534"
const val GITHUB_PROJECT = "https://github.com/h4h13/RetroMusicPlayer"
const val TELEGRAM_CHANGE_LOG = "https://t.me/retromusiclog"
const val USER_PROFILE = "profile.jpg"
const val USER_BANNER = "banner.jpg"
const val APP_INSTAGRAM_LINK = "https://www.instagram.com/retromusicapp/"
const val APP_TELEGRAM_LINK = "https://t.me/retromusicapp/"
const val APP_TWITTER_LINK = "https://twitter.com/retromusicapp"
const val FAQ_LINK = "https://github.com/h4h13/RetroMusicPlayer/blob/master/FAQ.md"
const val PINTEREST = "https://in.pinterest.com/retromusicapp/"
const val IS_MUSIC =
MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"
val baseProjection = arrayOf(
BaseColumns._ID, // 0
MediaStore.Audio.AudioColumns.TITLE, // 1
MediaStore.Audio.AudioColumns.TRACK, // 2
MediaStore.Audio.AudioColumns.YEAR, // 3
MediaStore.Audio.AudioColumns.DURATION, // 4
MediaStore.Audio.AudioColumns.DATA, // 5
MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6
MediaStore.Audio.AudioColumns.ALBUM_ID, // 7
MediaStore.Audio.AudioColumns.ALBUM, // 8
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
MediaStore.Audio.AudioColumns.ARTIST,// 10
MediaStore.Audio.AudioColumns.COMPOSER// 11
)
const val NUMBER_OF_TOP_TRACKS = 99
}
const val EXTRA_SONG = "extra_songs"
const val EXTRA_PLAYLIST = "extra_list"
const val LIBRARY_CATEGORIES = "library_categories"
const val EXTRA_SONG_INFO = "extra_song_info"
const val DESATURATED_COLOR = "desaturated_color"
const val BLACK_THEME = "black_theme"
const val KEEP_SCREEN_ON = "keep_screen_on"
const val TOGGLE_HOME_BANNER = "toggle_home_banner"
const val NOW_PLAYING_SCREEN_ID = "now_playing_screen_id"
const val CAROUSEL_EFFECT = "carousel_effect"
const val COLORED_NOTIFICATION = "colored_notification"
const val CLASSIC_NOTIFICATION = "classic_notification"
const val GAPLESS_PLAYBACK = "gapless_playback"
const val ALBUM_ART_ON_LOCKSCREEN = "album_art_on_lockscreen"
const val BLURRED_ALBUM_ART = "blurred_album_art"
const val NEW_BLUR_AMOUNT = "new_blur_amount"
const val TOGGLE_HEADSET = "toggle_headset"
const val GENERAL_THEME = "general_theme"
const val CIRCULAR_ALBUM_ART = "circular_album_art"
const val USER_NAME = "user_name"
const val TOGGLE_FULL_SCREEN = "toggle_full_screen"
const val TOGGLE_VOLUME = "toggle_volume"
const val ROUND_CORNERS = "corner_window"
const val TOGGLE_GENRE = "toggle_genre"
const val PROFILE_IMAGE_PATH = "profile_image_path"
const val BANNER_IMAGE_PATH = "banner_image_path"
const val ADAPTIVE_COLOR_APP = "adaptive_color_app"
const val TOGGLE_SEPARATE_LINE = "toggle_separate_line"
const val HOME_ARTIST_GRID_STYLE = "home_artist_grid_style"
const val TOGGLE_ADD_CONTROLS = "toggle_add_controls"
const val ALBUM_COVER_STYLE = "album_cover_style_id"
const val ALBUM_COVER_TRANSFORM = "album_cover_transform"
const val TAB_TEXT_MODE = "tab_text_mode"
const val LANGUAGE_NAME = "language_name"
const val DIALOG_CORNER = "dialog_corner"
const val SLEEP_TIMER_FINISH_SONG = "sleep_timer_finish_song"
const val ALBUM_GRID_STYLE = "album_grid_style_home"
const val ARTIST_GRID_STYLE = "artist_grid_style_home"
const val SAF_SDCARD_URI = "saf_sdcard_uri"
const val SONG_SORT_ORDER = "song_sort_order"
const val SONG_GRID_SIZE = "song_grid_size"
const val GENRE_SORT_ORDER = "genre_sort_order"
const val LAST_PAGE = "last_start_page"
const val BLUETOOTH_PLAYBACK = "bluetooth_playback"
const val INITIALIZED_BLACKLIST = "initialized_blacklist"
const val ARTIST_SORT_ORDER = "artist_sort_order"
const val ARTIST_ALBUM_SORT_ORDER = "artist_album_sort_order"
const val ALBUM_SORT_ORDER = "album_sort_order"
const val ALBUM_SONG_SORT_ORDER = "album_song_sort_order"
const val ARTIST_SONG_SORT_ORDER = "artist_song_sort_order"
const val ALBUM_GRID_SIZE = "album_grid_size"
const val ALBUM_GRID_SIZE_LAND = "album_grid_size_land"
const val SONG_GRID_SIZE_LAND = "song_grid_size_land"
const val ARTIST_GRID_SIZE = "artist_grid_size"
const val ARTIST_GRID_SIZE_LAND = "artist_grid_size_land"
const val COLORED_APP_SHORTCUTS = "colored_app_shortcuts"
const val AUDIO_DUCKING = "audio_ducking"
const val LAST_ADDED_CUTOFF = "last_added_interval"
const val LAST_SLEEP_TIMER_VALUE = "last_sleep_timer_value"
const val NEXT_SLEEP_TIMER_ELAPSED_REALTIME = "next_sleep_timer_elapsed_real_time"
const val IGNORE_MEDIA_STORE_ARTWORK = "ignore_media_store_artwork"
const val LAST_CHANGELOG_VERSION = "last_changelog_version"
const val AUTO_DOWNLOAD_IMAGES_POLICY = "auto_download_images_policy"
const val START_DIRECTORY = "start_directory"
const val LOCK_SCREEN = "lock_screen"
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position"
const val CHOOSE_EQUALIZER = "choose_equalizer"
const val TOGGLE_SHUFFLE = "toggle_shuffle"
const val SONG_GRID_STYLE = "song_grid_style"
const val PAUSE_ON_ZERO_VOLUME = "pause_on_zero_volume"
const val FILTER_SONG = "filter_song"
const val EXPAND_NOW_PLAYING_PANEL = "expand_now_playing_panel"

View file

@ -1,43 +0,0 @@
package code.name.monkey.retromusic;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.LocaleList;
import java.util.Locale;
import code.name.monkey.appthemehelper.util.VersionUtils;
public class LanguageContextWrapper extends ContextWrapper {
public LanguageContextWrapper(Context base) {
super(base);
}
public static LanguageContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (VersionUtils.INSTANCE.hasNougatMR()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (VersionUtils.INSTANCE.hasLollipop()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new LanguageContextWrapper(context);
}
}

View file

@ -1,26 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic
/**
* Created by hemanths on 2019-10-23.
*/
sealed class Result<out T : Any> {
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable) : Result<Nothing>()
}

View file

@ -1,39 +0,0 @@
package code.name.monkey.retromusic;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import org.jetbrains.annotations.NotNull;
public class RetroBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
private static final String TAG = "RetroBottomSheetBehavior";
private boolean allowDragging = true;
public RetroBottomSheetBehavior() {
}
public RetroBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setAllowDragging(boolean allowDragging) {
this.allowDragging = allowDragging;
}
@Override
public boolean onInterceptTouchEvent(@NotNull CoordinatorLayout parent, @NotNull V child, @NotNull MotionEvent event) {
if (!allowDragging) {
return false;
}
return super.onInterceptTouchEvent(parent, child, event);
}
}

View file

@ -1,148 +0,0 @@
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.retromusic.App
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.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.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.adapter.ContributorAdapter
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.model.Contributor
import code.name.monkey.retromusic.util.NavigationUtil
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 contributorsJson: 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?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
applyToolbar(toolbar)
version.setSummary(getAppVersion())
setUpView()
loadContributors()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
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)
bugReportLink.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.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 -> openUrl(TELEGRAM_CHANGE_LOG)
R.id.openSource -> NavigationUtil.goToOpenSource(this)
R.id.bugReportLink -> NavigationUtil.bugReport(this)
}
}
private fun getAppVersion(): String {
return try {
val isPro = if (App.isProVersion()) "Pro" else "Free"
val packageInfo = packageManager.getPackageInfo(packageName, 0)
"${packageInfo.versionName} $isPro"
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
"0.0.0"
}
}
private fun shareApp() {
ShareCompat.IntentBuilder.from(this).setType("text/plain")
.setChooserTitle(R.string.share_app)
.setText(String.format(getString(R.string.app_share), packageName)).startChooser()
}
private fun loadContributors() {
val type = object : TypeToken<List<Contributor>>() {
}.type
val contributors = Gson().fromJson<List<Contributor>>(contributorsJson, type)
val contributorAdapter = ContributorAdapter(contributors)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.itemAnimator = DefaultItemAnimator()
recyclerView.adapter = contributorAdapter
}
}

View file

@ -1,237 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities
import android.animation.ObjectAnimator
import android.graphics.Color
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.animation.LinearInterpolator
import android.widget.SeekBar
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.fragments.base.AbsPlayerControlsFragment
import code.name.monkey.retromusic.glide.BlurTransformation
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper.Callback
import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_drive_mode.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Created by hemanths on 2020-02-02.
*/
class DriveModeActivity : AbsMusicServiceActivity(), Callback {
private var lastPlaybackControlsColor: Int = Color.GRAY
private var lastDisabledPlaybackControlsColor: Int = Color.GRAY
private lateinit var progressViewUpdateHelper: MusicProgressViewUpdateHelper
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_drive_mode)
setUpMusicControllers()
progressViewUpdateHelper = MusicProgressViewUpdateHelper(this)
lastPlaybackControlsColor = ThemeStore.accentColor(this)
close.setOnClickListener {
onBackPressed()
}
}
private fun setUpMusicControllers() {
setUpPlayPauseFab()
setUpPrevNext()
setUpRepeatButton()
setUpShuffleButton()
setUpProgressSlider()
setupFavouriteToggle()
}
private fun setupFavouriteToggle() {
songFavourite.setOnClickListener {
MusicUtil.toggleFavorite(
this@DriveModeActivity,
MusicPlayerRemote.currentSong
)
}
}
private fun toggleFavourite() {
CoroutineScope(Dispatchers.IO).launch {
val isFavourite =
MusicUtil.isFavorite(this@DriveModeActivity, MusicPlayerRemote.currentSong)
withContext(Dispatchers.Main) {
songFavourite.setImageResource(if (isFavourite) R.drawable.ic_favorite_white_24dp else R.drawable.ic_favorite_border_white_24dp)
}
}
}
private fun setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(object : SimpleOnSeekbarChangeListener() {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
MusicPlayerRemote.seekTo(progress)
onUpdateProgressViews(
MusicPlayerRemote.songProgressMillis,
MusicPlayerRemote.songDurationMillis
)
}
}
})
}
override fun onPause() {
super.onPause()
progressViewUpdateHelper.stop()
}
override fun onResume() {
super.onResume()
progressViewUpdateHelper.start()
}
private fun setUpPrevNext() {
nextButton.setOnClickListener { MusicPlayerRemote.playNextSong() }
previousButton.setOnClickListener { MusicPlayerRemote.back() }
}
private fun setUpShuffleButton() {
shuffleButton.setOnClickListener { MusicPlayerRemote.toggleShuffleMode() }
}
private fun setUpRepeatButton() {
repeatButton.setOnClickListener { MusicPlayerRemote.cycleRepeatMode() }
}
private fun setUpPlayPauseFab() {
playPauseButton.setOnClickListener(PlayPauseButtonOnClickHandler())
}
override fun onRepeatModeChanged() {
super.onRepeatModeChanged()
updateRepeatState()
}
override fun onShuffleModeChanged() {
super.onShuffleModeChanged()
updateShuffleState()
}
override fun onPlayStateChanged() {
super.onPlayStateChanged()
updatePlayPauseDrawableState()
}
override fun onServiceConnected() {
super.onServiceConnected()
updatePlayPauseDrawableState()
updateSong()
updateRepeatState()
updateShuffleState()
toggleFavourite()
}
private fun updatePlayPauseDrawableState() {
if (MusicPlayerRemote.isPlaying) {
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp)
} else {
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp)
}
}
fun updateShuffleState() {
when (MusicPlayerRemote.shuffleMode) {
MusicService.SHUFFLE_MODE_SHUFFLE -> shuffleButton.setColorFilter(
lastPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
else -> shuffleButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
}
}
private fun updateRepeatState() {
when (MusicPlayerRemote.repeatMode) {
MusicService.REPEAT_MODE_NONE -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(
lastDisabledPlaybackControlsColor,
PorterDuff.Mode.SRC_IN
)
}
MusicService.REPEAT_MODE_ALL -> {
repeatButton.setImageResource(R.drawable.ic_repeat_white_24dp)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
}
MusicService.REPEAT_MODE_THIS -> {
repeatButton.setImageResource(R.drawable.ic_repeat_one_white_24dp)
repeatButton.setColorFilter(lastPlaybackControlsColor, PorterDuff.Mode.SRC_IN)
}
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
updateSong()
toggleFavourite()
}
private fun updateSong() {
val song = MusicPlayerRemote.currentSong
songTitle.text = song.title
songText.text = song.artistName
SongGlideRequest.Builder.from(Glide.with(this), song)
.checkIgnoreMediaStore(this)
.generatePalette(this)
.build()
.transform(BlurTransformation.Builder(this).build())
.into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(color: MediaNotificationProcessor) {
}
})
}
override fun onUpdateProgressViews(progress: Int, total: Int) {
progressSlider.max = total
val animator = ObjectAnimator.ofInt(progressSlider, "progress", progress)
animator.duration = AbsPlayerControlsFragment.SLIDER_ANIMATION_TIME
animator.interpolator = LinearInterpolator()
animator.start()
songTotalTime.text = MusicUtil.getReadableDurationString(total.toLong())
songCurrentProgress.text = MusicUtil.getReadableDurationString(progress.toLong())
}
}

View file

@ -1,162 +0,0 @@
package code.name.monkey.retromusic.activities
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.util.ATHUtil
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.song.ShuffleButtonSongAdapter
import code.name.monkey.retromusic.extensions.applyToolbar
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.presenter.GenreDetailsPresenter
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsView
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import com.afollestad.materialcab.MaterialCab
import kotlinx.android.synthetic.main.activity_playlist_detail.*
import java.util.*
import javax.inject.Inject
/**
* @author Hemanth S (h4h13).
*/
class GenreDetailsActivity : AbsSlidingMusicPanelActivity(), CabHolder, GenreDetailsView {
@Inject
lateinit var genreDetailsPresenter: GenreDetailsPresenter
private lateinit var genre: Genre
private lateinit var songAdapter: ShuffleButtonSongAdapter
private var cab: MaterialCab? = null
private fun getEmojiByUnicode(unicode: Int): String {
return String(Character.toChars(unicode))
}
private fun checkIsEmpty() {
checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty?.visibility = if (songAdapter.itemCount == 0) View.VISIBLE else View.GONE
}
private fun checkForPadding() {
val height = DensityUtil.dip2px(this, 52f)
recyclerView.setPadding(0, 0, 0, (height))
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setBottomBarVisibility(View.GONE)
if (intent.extras != null) {
genre = intent?.extras?.getParcelable(EXTRA_GENRE_ID)!!
} else {
finish()
}
setUpToolBar()
setupRecyclerView()
App.musicComponent.inject(this)
genreDetailsPresenter.attachView(this)
}
private fun setUpToolBar() {
applyToolbar(toolbar)
title = genre.name
}
override fun onResume() {
super.onResume()
genreDetailsPresenter.loadGenreSongs(genre.id)
}
override fun onDestroy() {
super.onDestroy()
genreDetailsPresenter.detachView()
}
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail)
}
override fun showEmptyView() {
}
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() {
songAdapter = ShuffleButtonSongAdapter(this, ArrayList(), R.layout.item_list, this)
recyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = LinearLayoutManager(this@GenreDetailsActivity)
adapter = songAdapter
}
songAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
super.onChanged()
checkIsEmpty()
}
})
}
override fun songs(songs: List<Song>) {
songAdapter.swapDataSet(songs)
}
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(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
).start(callback)
return cab!!
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive) cab!!.finish()
else {
recyclerView!!.stopScroll()
super.onBackPressed()
}
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
genreDetailsPresenter.loadGenreSongs(genre.id)
}
companion object {
const val EXTRA_GENRE_ID = "extra_genre_id"
}
}

View file

@ -1,99 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
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;
/**
* Created by hemanths on 2019-09-27.
*/
public class LicenseActivity extends AbsBaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_license);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setLightNavigationBar(true);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ToolbarContentTintHelper.colorBackButton(toolbar);
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
WebView webView = findViewById(R.id.license);
try {
StringBuilder buf = new StringBuilder();
InputStream json = getAssets().open("oldindex.html");
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
// Inject color values for WebView body background and links
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
final String backgroundColor = colorToCSS(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface,
Color.parseColor(isDark ? "#424242" : "#ffffff")));
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
final String changeLog = buf.toString()
.replace("{style-placeholder}",
String.format("body { background-color: %s; color: %s; }", backgroundColor, contentColor))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace("{link-color-active}",
colorToCSS(ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "text/html", "UTF-8");
} catch (Throwable e) {
webView.loadData("<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private String colorToCSS(int color) {
return String.format("rgb(%d, %d, %d)", Color.red(color), Color.green(color),
Color.blue(color)); // on API 29, WebView doesn't load with hex colors
}
}

View file

@ -1,100 +0,0 @@
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.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.fragments.player.lockscreen.LockScreenPlayerControlsFragment
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
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_lock_screen.*
class LockScreenActivity : AbsMusicServiceActivity() {
private var fragment: LockScreenPlayerControlsFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
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
)
}
setContentView(R.layout.activity_lock_screen)
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(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val keyguardManager =
getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(this@LockScreenActivity, null)
}
finish()
return true
}
}).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()
}
}
override fun onPlayingMetaChanged() {
super.onPlayingMetaChanged()
updateSongs()
}
override fun onServiceConnected() {
super.onServiceConnected()
updateSongs()
}
private fun updateSongs() {
val song = MusicPlayerRemote.currentSong
SongGlideRequest.Builder.from(Glide.with(this), song).checkIgnoreMediaStore(this)
.generatePalette(this).build().dontAnimate()
.into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
fragment?.setColor(colors)
}
})
}
}

View file

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

View file

@ -1,786 +0,0 @@
package code.name.monkey.retromusic.activities
import android.app.ActivityOptions
import android.content.*
import android.content.IntentSender.SendIntentException
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.SubMenu
import android.view.View
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog.Companion.create
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.albums.AlbumsFragment
import code.name.monkey.retromusic.fragments.artists.ArtistsFragment
import code.name.monkey.retromusic.fragments.base.AbsLibraryPagerRecyclerViewCustomGridSizeFragment
import code.name.monkey.retromusic.fragments.genres.GenresFragment
import code.name.monkey.retromusic.fragments.home.BannerHomeFragment
import code.name.monkey.retromusic.fragments.mainactivity.FoldersFragment
import code.name.monkey.retromusic.fragments.playlists.PlaylistsFragment
import code.name.monkey.retromusic.fragments.queue.PlayingQueueFragment
import code.name.monkey.retromusic.fragments.songs.SongsFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying
import code.name.monkey.retromusic.helper.MusicPlayerRemote.openAndShuffleQueue
import code.name.monkey.retromusic.helper.MusicPlayerRemote.openQueue
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playFromUri
import code.name.monkey.retromusic.helper.MusicPlayerRemote.shuffleMode
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
import code.name.monkey.retromusic.helper.SortOrder.*
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks
import code.name.monkey.retromusic.loaders.AlbumLoader.getAlbum
import code.name.monkey.retromusic.loaders.ArtistLoader.getArtist
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader.getPlaylistSongList
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.AppRater.appLaunched
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialcab.MaterialCab
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.snackbar.Snackbar
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.InstallState
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.InstallStatus.DOWNLOADED
import com.google.android.play.core.install.model.InstallStatus.INSTALLED
import com.google.android.play.core.install.model.UpdateAvailability
import com.google.android.play.core.tasks.Task
import kotlinx.android.synthetic.main.activity_main_content.*
import java.util.*
class MainActivity : AbsSlidingMusicPanelActivity(),
SharedPreferences.OnSharedPreferenceChangeListener, CabHolder {
companion object {
const val TAG = "MainActivity"
const val EXPAND_PANEL = "expand_panel"
const val APP_UPDATE_REQUEST_CODE = 9002
}
lateinit var libraryViewModel: LibraryViewModel
private var cab: MaterialCab? = null
private val intentFilter = IntentFilter(Intent.ACTION_SCREEN_OFF)
private lateinit var currentFragment: MainActivityFragmentCallbacks
private var appUpdateManager: AppUpdateManager? = null
private var blockRequestPermissions = false
private val listener = object : InstallStateUpdatedListener {
override fun onStateUpdate(state: InstallState) {
when {
state.installStatus() == DOWNLOADED -> {
popupSnackBarForCompleteUpdate()
}
state.installStatus() == INSTALLED -> {
appUpdateManager?.unregisterListener(this)
}
else -> {
Log.i(TAG, "InstallStateUpdatedListener: state: " + state.installStatus())
}
}
}
}
private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action != null && action == Intent.ACTION_SCREEN_OFF) {
if (PreferenceUtil.isLockScreen && 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 {
return wrapSlidingMusicPanel(R.layout.activity_main_content)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setLightNavigationBar(true)
setTaskDescriptionColorAuto()
hideStatusBar()
setBottomBarVisibility(View.VISIBLE)
libraryViewModel = ViewModelProvider(this).get(LibraryViewModel::class.java)
addMusicServiceEventListener(libraryViewModel)
if (savedInstanceState == null) {
selectedFragment(PreferenceUtil.lastPage)
} else {
restoreCurrentFragment()
}
appLaunched(this)
setupToolbar()
checkUpdate()
updateTabs()
getBottomNavigationView().selectedItemId = PreferenceUtil.lastPage
getBottomNavigationView().setOnNavigationItemSelectedListener {
PreferenceUtil.lastPage = it.itemId
selectedFragment(it.itemId)
true
}
}
override fun onResume() {
super.onResume()
registerReceiver(broadcastReceiver, intentFilter)
PreferenceUtil.registerOnSharedPreferenceChangedListener(this)
if (intent.hasExtra(EXPAND_PANEL) &&
intent.getBooleanExtra(EXPAND_PANEL, false) &&
PreferenceUtil.isExpandPanel
) {
expandPanel()
intent.removeExtra(EXPAND_PANEL)
}
appUpdateManager?.appUpdateInfo
?.addOnSuccessListener { appUpdateInfo: AppUpdateInfo ->
if (appUpdateInfo.installStatus() == DOWNLOADED) {
popupSnackBarForCompleteUpdate()
}
try {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
appUpdateManager!!.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
APP_UPDATE_REQUEST_CODE
)
}
} catch (e: SendIntentException) {
e.printStackTrace()
}
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(broadcastReceiver)
PreferenceUtil.unregisterOnSharedPreferenceChangedListener(this)
}
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
ToolbarContentTintHelper.handleOnPrepareOptionsMenu(this, toolbar)
return super.onPrepareOptionsMenu(menu)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
menu ?: return super.onCreateOptionsMenu(menu)
if (isPlaylistPage()) {
menu.add(0, R.id.action_new_playlist, 1, R.string.new_playlist_title)
.setIcon(R.drawable.ic_playlist_add_white_24dp)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
if (isHomePage()) {
menu.add(0, R.id.action_mic, 1, getString(R.string.action_search))
.setIcon(R.drawable.ic_mic_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
if (isFolderPage()) {
menu.add(0, R.id.action_scan, 0, R.string.scan_media)
.setIcon(R.drawable.ic_scanner_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM)
menu.add(0, R.id.action_go_to_start_directory, 1, R.string.action_go_to_start_directory)
.setIcon(R.drawable.ic_bookmark_music_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
val fragment: Fragment? = getCurrentFragment()
if (fragment != null && fragment is AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>) {
val gridSizeItem: MenuItem = menu.findItem(R.id.action_grid_size)
if (RetroUtil.isLandscape()) {
gridSizeItem.setTitle(R.string.action_grid_size_land)
}
setUpGridSizeMenu(fragment, gridSizeItem.subMenu)
setupLayoutMenu(fragment, menu.findItem(R.id.action_layout_type).subMenu)
setUpSortOrderMenu(fragment, menu.findItem(R.id.action_sort_order).subMenu)
} else {
menu.removeItem(R.id.action_layout_type)
menu.removeItem(R.id.action_grid_size)
menu.removeItem(R.id.action_sort_order)
}
menu.add(0, R.id.action_settings, 6, getString(R.string.action_settings))
.setIcon(R.drawable.ic_settings_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM)
menu.add(0, R.id.action_search, 0, getString(R.string.action_search))
.setIcon(R.drawable.ic_search_white_24dp)
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS)
ToolbarContentTintHelper.handleOnCreateOptionsMenu(
this,
toolbar,
menu,
getToolbarBackgroundColor(toolbar)
)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val fragment = getCurrentFragment()
if (fragment is AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>) {
if (handleGridSizeMenuItem(fragment, item)) {
return true
}
if (handleLayoutResType(fragment, item)) {
return true
}
if (handleSortOrderMenuItem(fragment, item)) {
return true
}
}
when (item.itemId) {
R.id.action_search -> NavigationUtil.goToSearch(this)
R.id.action_new_playlist -> {
create().show(supportFragmentManager, "CREATE_PLAYLIST")
return true
}
R.id.action_mic -> {
val options = ActivityOptions.makeSceneTransitionAnimation(
this, toolbar,
getString(R.string.transition_toolbar)
)
NavigationUtil.goToSearch(this, true, options)
return true
}
R.id.action_settings -> {
NavigationUtil.goToSettings(this)
return true
}
}
return super.onOptionsItemSelected(item)
}
private fun handleSortOrderMenuItem(
fragment: AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>,
item: MenuItem
): Boolean {
var sortOrder: String? = null
when (fragment) {
is AlbumsFragment -> {
when (item.itemId) {
R.id.action_album_sort_order_asc -> sortOrder = AlbumSortOrder.ALBUM_A_Z
R.id.action_album_sort_order_desc -> sortOrder = AlbumSortOrder.ALBUM_Z_A
R.id.action_album_sort_order_artist -> sortOrder = AlbumSortOrder.ALBUM_ARTIST
R.id.action_album_sort_order_year -> sortOrder = AlbumSortOrder.ALBUM_YEAR
}
}
is ArtistsFragment -> {
when (item.itemId) {
R.id.action_artist_sort_order_asc -> sortOrder = ArtistSortOrder.ARTIST_A_Z
R.id.action_artist_sort_order_desc -> sortOrder = ArtistSortOrder.ARTIST_Z_A
}
}
is SongsFragment -> {
when (item.itemId) {
R.id.action_song_sort_order_asc -> sortOrder = SongSortOrder.SONG_A_Z
R.id.action_song_sort_order_desc -> sortOrder = SongSortOrder.SONG_Z_A
R.id.action_song_sort_order_artist -> sortOrder = SongSortOrder.SONG_ARTIST
R.id.action_song_sort_order_album -> sortOrder = SongSortOrder.SONG_ALBUM
R.id.action_song_sort_order_year -> sortOrder = SongSortOrder.SONG_YEAR
R.id.action_song_sort_order_date -> sortOrder = SongSortOrder.SONG_DATE
R.id.action_song_sort_order_composer -> sortOrder = SongSortOrder.COMPOSER
R.id.action_song_sort_order_date_modified ->
sortOrder = SongSortOrder.SONG_DATE_MODIFIED
}
}
}
if (sortOrder != null) {
item.isChecked = true
fragment.setAndSaveSortOrder(sortOrder)
return true
}
return false
}
private fun handleLayoutResType(
fragment: AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>,
item: MenuItem
): Boolean {
var layoutRes = -1
when (item.itemId) {
R.id.action_layout_normal -> layoutRes = R.layout.item_grid
R.id.action_layout_card -> layoutRes = R.layout.item_card
R.id.action_layout_colored_card -> layoutRes = R.layout.item_card_color
R.id.action_layout_circular -> layoutRes = R.layout.item_grid_circle
R.id.action_layout_image -> layoutRes = R.layout.image
R.id.action_layout_gradient_image -> layoutRes = R.layout.item_image_gradient
}
if (layoutRes != -1) {
item.isChecked = true
fragment.setAndSaveLayoutRes(layoutRes)
return true
}
return false
}
private fun handleGridSizeMenuItem(
fragment: AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>,
item: MenuItem
): Boolean {
var gridSize = 0
when (item.itemId) {
R.id.action_grid_size_1 -> gridSize = 1
R.id.action_grid_size_2 -> gridSize = 2
R.id.action_grid_size_3 -> gridSize = 3
R.id.action_grid_size_4 -> gridSize = 4
R.id.action_grid_size_5 -> gridSize = 5
R.id.action_grid_size_6 -> gridSize = 6
R.id.action_grid_size_7 -> gridSize = 7
R.id.action_grid_size_8 -> gridSize = 8
}
if (gridSize > 0) {
item.isChecked = true
fragment.setAndSaveGridSize(gridSize)
return true
}
return false
}
private fun setUpGridSizeMenu(
fragment: AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>,
gridSizeMenu: SubMenu
) {
when (fragment.getGridSize()) {
1 -> gridSizeMenu.findItem(R.id.action_grid_size_1).isChecked = true
2 -> gridSizeMenu.findItem(R.id.action_grid_size_2).isChecked = true
3 -> gridSizeMenu.findItem(R.id.action_grid_size_3).isChecked = true
4 -> gridSizeMenu.findItem(R.id.action_grid_size_4).isChecked = true
5 -> gridSizeMenu.findItem(R.id.action_grid_size_5).isChecked = true
6 -> gridSizeMenu.findItem(R.id.action_grid_size_6).isChecked = true
7 -> gridSizeMenu.findItem(R.id.action_grid_size_7).isChecked = true
8 -> gridSizeMenu.findItem(R.id.action_grid_size_8).isChecked = true
}
val maxGridSize = fragment.maxGridSize
if (maxGridSize < 8) {
gridSizeMenu.findItem(R.id.action_grid_size_8).isVisible = false
}
if (maxGridSize < 7) {
gridSizeMenu.findItem(R.id.action_grid_size_7).isVisible = false
}
if (maxGridSize < 6) {
gridSizeMenu.findItem(R.id.action_grid_size_6).isVisible = false
}
if (maxGridSize < 5) {
gridSizeMenu.findItem(R.id.action_grid_size_5).isVisible = false
}
if (maxGridSize < 4) {
gridSizeMenu.findItem(R.id.action_grid_size_4).isVisible = false
}
if (maxGridSize < 3) {
gridSizeMenu.findItem(R.id.action_grid_size_3).isVisible = false
}
}
private fun setupLayoutMenu(
fragment: AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>,
subMenu: SubMenu
) {
when (fragment.itemLayoutRes()) {
R.layout.item_card ->
subMenu.findItem(R.id.action_layout_card).isChecked = true
R.layout.item_card_color ->
subMenu.findItem(R.id.action_layout_colored_card).isChecked = true
R.layout.item_grid_circle ->
subMenu.findItem(R.id.action_layout_circular).isChecked = true
R.layout.image ->
subMenu.findItem(R.id.action_layout_image).isChecked = true
R.layout.item_image_gradient ->
subMenu.findItem(R.id.action_layout_gradient_image).isChecked = true
R.layout.item_grid ->
subMenu.findItem(R.id.action_layout_normal).isChecked = true
else ->
subMenu.findItem(R.id.action_layout_normal).isChecked = true
}
}
private fun setUpSortOrderMenu(
fragment: AbsLibraryPagerRecyclerViewCustomGridSizeFragment<*, *>,
sortOrderMenu: SubMenu
) {
val currentSortOrder = fragment.getSortOrder()
sortOrderMenu.clear()
when (fragment) {
is AlbumsFragment -> {
sortOrderMenu.add(
0,
R.id.action_album_sort_order_asc,
0,
R.string.sort_order_a_z
).isChecked = currentSortOrder == AlbumSortOrder.ALBUM_A_Z
sortOrderMenu.add(
0,
R.id.action_album_sort_order_desc,
1,
R.string.sort_order_z_a
).isChecked =
currentSortOrder == AlbumSortOrder.ALBUM_Z_A
sortOrderMenu.add(
0,
R.id.action_album_sort_order_artist,
2,
R.string.sort_order_artist
).isChecked =
currentSortOrder == AlbumSortOrder.ALBUM_ARTIST
sortOrderMenu.add(
0,
R.id.action_album_sort_order_year,
3,
R.string.sort_order_year
).isChecked =
currentSortOrder == AlbumSortOrder.ALBUM_YEAR
}
is ArtistsFragment -> {
sortOrderMenu.add(
0,
R.id.action_artist_sort_order_asc,
0,
R.string.sort_order_a_z
).isChecked =
currentSortOrder == ArtistSortOrder.ARTIST_A_Z
sortOrderMenu.add(
0,
R.id.action_artist_sort_order_desc,
1,
R.string.sort_order_z_a
).isChecked =
currentSortOrder == ArtistSortOrder.ARTIST_Z_A
}
is SongsFragment -> {
sortOrderMenu.add(
0,
R.id.action_song_sort_order_asc,
0,
R.string.sort_order_a_z
).isChecked =
currentSortOrder == SongSortOrder.SONG_A_Z
sortOrderMenu.add(
0,
R.id.action_song_sort_order_desc,
1,
R.string.sort_order_z_a
).isChecked =
currentSortOrder == SongSortOrder.SONG_Z_A
sortOrderMenu.add(
0,
R.id.action_song_sort_order_artist,
2,
R.string.sort_order_artist
).isChecked =
currentSortOrder == SongSortOrder.SONG_ARTIST
sortOrderMenu.add(
0,
R.id.action_song_sort_order_album,
3,
R.string.sort_order_album
).isChecked =
currentSortOrder == SongSortOrder.SONG_ALBUM
sortOrderMenu.add(
0,
R.id.action_song_sort_order_year,
4,
R.string.sort_order_year
).isChecked =
currentSortOrder == SongSortOrder.SONG_YEAR
sortOrderMenu.add(
0,
R.id.action_song_sort_order_date,
5,
R.string.sort_order_date
).isChecked =
currentSortOrder == SongSortOrder.SONG_DATE
sortOrderMenu.add(
0,
R.id.action_song_sort_order_date_modified,
6,
R.string.sort_order_date_modified
).isChecked = currentSortOrder == SongSortOrder.SONG_DATE_MODIFIED
sortOrderMenu.add(
0,
R.id.action_song_sort_order_composer,
7,
R.string.sort_order_composer
).isChecked = currentSortOrder == SongSortOrder.COMPOSER
}
}
sortOrderMenu.setGroupCheckable(0, true, true)
}
private fun getCurrentFragment(): Fragment? {
return supportFragmentManager.findFragmentById(R.id.fragment_container)
}
private fun isFolderPage(): Boolean {
return supportFragmentManager.findFragmentByTag(FoldersFragment.TAG) is FoldersFragment
}
private fun isHomePage(): Boolean {
return supportFragmentManager.findFragmentByTag(BannerHomeFragment.TAG) is BannerHomeFragment
}
private fun isPlaylistPage(): Boolean {
return supportFragmentManager.findFragmentByTag(PlaylistsFragment.TAG) is PlaylistsFragment
}
fun addOnAppBarOffsetChangedListener(
changedListener: AppBarLayout.OnOffsetChangedListener
) {
appBarLayout.addOnOffsetChangedListener(changedListener)
}
fun removeOnAppBarOffsetChangedListener(
changedListener: AppBarLayout.OnOffsetChangedListener
) {
appBarLayout.removeOnOffsetChangedListener(changedListener)
}
fun getTotalAppBarScrollingRange(): Int {
return appBarLayout.totalScrollRange
}
override fun requestPermissions() {
if (!blockRequestPermissions) {
super.requestPermissions()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (!hasPermissions()) {
requestPermissions()
}
}
private fun setupToolbar() {
toolbar.setBackgroundColor(resolveColor(this, R.attr.colorSurface))
appBarLayout.setBackgroundColor(resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
}
private fun checkUpdate() {
appUpdateManager = AppUpdateManagerFactory.create(this)
appUpdateManager?.registerListener(listener)
val appUpdateInfoTask: Task<AppUpdateInfo>? = appUpdateManager?.appUpdateInfo
appUpdateInfoTask?.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
) {
try {
appUpdateManager?.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
APP_UPDATE_REQUEST_CODE
)
} catch (e: SendIntentException) {
e.printStackTrace()
}
}
}
}
private fun popupSnackBarForCompleteUpdate() {
val snackBar =
Snackbar.make(
findViewById(R.id.mainContent),
"New app is ready!",
Snackbar.LENGTH_INDEFINITE
)
snackBar.setAction(
"Install"
) {
appUpdateManager?.completeUpdate()
}
snackBar.setActionTextColor(accentColor(this))
snackBar.show()
}
private fun setCurrentFragment(
fragment: Fragment,
tag: String
) {
supportFragmentManager.commit {
replace(R.id.fragment_container, fragment, tag)
}
currentFragment = fragment as MainActivityFragmentCallbacks
}
private fun selectedFragment(itemId: Int) {
when (itemId) {
R.id.action_album -> setCurrentFragment(
AlbumsFragment.newInstance(),
AlbumsFragment.TAG
)
R.id.action_artist -> setCurrentFragment(
ArtistsFragment.newInstance(),
ArtistsFragment.TAG
)
R.id.action_playlist -> setCurrentFragment(
PlaylistsFragment.newInstance(),
PlaylistsFragment.TAG
)
R.id.action_genre -> setCurrentFragment(
GenresFragment.newInstance(),
GenresFragment.TAG
)
R.id.action_playing_queue -> setCurrentFragment(
PlayingQueueFragment.newInstance(),
PlayingQueueFragment.TAG
)
R.id.action_song -> setCurrentFragment(
SongsFragment.newInstance(),
SongsFragment.TAG
)
R.id.action_folder -> setCurrentFragment(
FoldersFragment.newInstance(this),
FoldersFragment.TAG
)
R.id.action_home -> setCurrentFragment(
BannerHomeFragment.newInstance(),
BannerHomeFragment.TAG
)
else -> setCurrentFragment(
BannerHomeFragment.newInstance(),
BannerHomeFragment.TAG
)
}
}
private fun restoreCurrentFragment() {
val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
if (fragment != null) {
currentFragment = fragment as MainActivityFragmentCallbacks
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == GENERAL_THEME || key == BLACK_THEME || key == ADAPTIVE_COLOR_APP || key == USER_NAME || key == TOGGLE_FULL_SCREEN || key == TOGGLE_VOLUME || key == ROUND_CORNERS || key == CAROUSEL_EFFECT || key == NOW_PLAYING_SCREEN_ID || key == TOGGLE_GENRE || key == BANNER_IMAGE_PATH || key == PROFILE_IMAGE_PATH || key == CIRCULAR_ALBUM_ART || key == KEEP_SCREEN_ON || key == TOGGLE_SEPARATE_LINE || key == TOGGLE_HOME_BANNER || key == TOGGLE_ADD_CONTROLS || key == ALBUM_COVER_STYLE || key == HOME_ARTIST_GRID_STYLE || key == ALBUM_COVER_TRANSFORM || key == DESATURATED_COLOR || key == EXTRA_SONG_INFO || key == TAB_TEXT_MODE || key == LANGUAGE_NAME || key == LIBRARY_CATEGORIES
) {
postRecreate()
}
}
override fun onServiceConnected() {
super.onServiceConnected()
handlePlaybackIntent(intent)
}
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: List<Song> =
getSongs(this, intent.extras!!)
if (shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
openAndShuffleQueue(songs, true)
} else {
openQueue(songs, 0, true)
}
handled = true
}
if (uri != null && uri.toString().isNotEmpty()) {
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: List<Song> =
ArrayList(getPlaylistSongList(this, id))
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)
openQueue(getAlbum(this, id).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)
openQueue(getArtist(this, id).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 = idString.toLong()
} catch (e: NumberFormatException) {
Log.e(TAG, e.message)
}
}
}
return id
}
override fun handleBackPress(): Boolean {
if (cab != null && cab!!.isActive) {
cab?.finish()
return true
}
return super.handleBackPress() || currentFragment.handleBackPress()
}
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
if (it.isActive) it.finish()
}
cab = MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
)
.start(callback)
return cab as MaterialCab
}
}

View file

@ -1,187 +0,0 @@
package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.MenuItem
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.util.MusicUtil
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils
import kotlinx.android.synthetic.main.activity_playing_queue.*
open class PlayingQueueActivity : AbsMusicServiceActivity() {
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
private var recyclerViewSwipeManager: RecyclerViewSwipeManager? = null
private var recyclerViewTouchActionGuardManager: RecyclerViewTouchActionGuardManager? = null
private var playingQueueAdapter: PlayingQueueAdapter? = null
private lateinit var linearLayoutManager: LinearLayoutManager
private fun getUpNextAndQueueTime(): String {
val duration = MusicPlayerRemote.getQueueDurationMillis(MusicPlayerRemote.position)
return MusicUtil.buildInfoString(
resources.getString(R.string.up_next),
MusicUtil.getReadableDurationString(duration)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_playing_queue)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setupToolbar()
setUpRecyclerView()
clearQueue.setOnClickListener {
MusicPlayerRemote.clearQueue()
}
checkForPadding()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun setUpRecyclerView() {
recyclerViewTouchActionGuardManager = RecyclerViewTouchActionGuardManager()
recyclerViewDragDropManager = RecyclerViewDragDropManager()
recyclerViewSwipeManager = RecyclerViewSwipeManager()
val animator = DraggableItemAnimator()
animator.supportsChangeAnimations = false
playingQueueAdapter = PlayingQueueAdapter(
this,
MusicPlayerRemote.playingQueue.toMutableList(),
MusicPlayerRemote.position,
R.layout.item_queue
)
wrappedAdapter = recyclerViewDragDropManager?.createWrappedAdapter(playingQueueAdapter!!)
wrappedAdapter = wrappedAdapter?.let { recyclerViewSwipeManager?.createWrappedAdapter(it) }
linearLayoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = wrappedAdapter
recyclerView.itemAnimator = animator
recyclerViewTouchActionGuardManager?.attachRecyclerView(recyclerView)
recyclerViewDragDropManager?.attachRecyclerView(recyclerView)
recyclerViewSwipeManager?.attachRecyclerView(recyclerView)
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()
} else if (dy < 0) {
clearQueue.extend()
}
}
})
//ViewUtil.setUpFastScrollRecyclerViewColor(this, recyclerView)
}
private fun checkForPadding() {
}
override fun onQueueChanged() {
if (MusicPlayerRemote.playingQueue.isEmpty()) {
finish()
return
}
checkForPadding()
updateQueue()
updateCurrentSong()
}
override fun onMediaStoreChanged() {
updateQueue()
updateCurrentSong()
}
private fun updateCurrentSong() {
toolbar.subtitle = getUpNextAndQueueTime()
}
override fun onPlayingMetaChanged() {
updateQueuePosition()
}
private fun updateQueuePosition() {
playingQueueAdapter?.setCurrent(MusicPlayerRemote.position)
resetToCurrentPosition()
toolbar.subtitle = getUpNextAndQueueTime()
}
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 (recyclerViewSwipeManager != null) {
recyclerViewSwipeManager?.release()
recyclerViewSwipeManager = null
}
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter)
wrappedAdapter = null
}
playingQueueAdapter = null
super.onDestroy()
}
private fun setupToolbar() {
toolbar.subtitle = getUpNextAndQueueTime()
toolbar.setBackgroundColor(surfaceColor())
setSupportActionBar(toolbar)
clearQueue.backgroundTintList = ColorStateList.valueOf(accentColor())
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
).apply {
clearQueue.setTextColor(this)
clearQueue.iconTint = this
}
}
}

View file

@ -1,245 +0,0 @@
package code.name.monkey.retromusic.activities
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.util.ATHUtil
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
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.extensions.applyToolbar
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.presenter.PlaylistSongsPresenter
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsView
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.PlaylistsUtil
import code.name.monkey.retromusic.util.RetroColorUtil
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 javax.inject.Inject
class PlaylistDetailActivity : AbsSlidingMusicPanelActivity(), CabHolder, PlaylistSongsView {
@Inject
lateinit var playlistSongsPresenter: PlaylistSongsPresenter
private lateinit var playlist: Playlist
private var cab: MaterialCab? = null
private lateinit var adapter: SongAdapter
private var wrappedAdapter: RecyclerView.Adapter<*>? = null
private var recyclerViewDragDropManager: RecyclerViewDragDropManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setBottomBarVisibility(View.GONE)
App.musicComponent.inject(this)
playlistSongsPresenter.attachView(this)
if (intent.extras != null) {
playlist = intent.extras!!.getParcelable(EXTRA_PLAYLIST)!!
} else {
finish()
}
setUpToolBar()
setUpRecyclerView()
}
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_playlist_detail)
}
private fun setUpRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this)
if (playlist is AbsCustomPlaylist) {
adapter = PlaylistSongAdapter(this, ArrayList(), R.layout.item_list, this)
recyclerView.adapter = adapter
} else {
recyclerViewDragDropManager = RecyclerViewDragDropManager()
val animator = RefactoredDefaultItemAnimator()
adapter = OrderablePlaylistSongAdapter(this,
ArrayList(),
R.layout.item_list,
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()
}
})
}
override fun onResume() {
super.onResume()
playlistSongsPresenter.loadPlaylistSongs(playlist)
}
private fun setUpToolBar() {
applyToolbar(toolbar)
title = playlist.name
}
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 {
when (item.itemId) {
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(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
).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)
setToolbarTitle(playlist.name)
}
}
playlistSongsPresenter.loadPlaylistSongs(playlist)
}
private fun setToolbarTitle(title: String) {
supportActionBar?.title = title
}
private fun checkForPadding() {
val height = DensityUtil.dip2px(this, 52f)
recyclerView.setPadding(0, 0, 0, (height))
}
private fun checkIsEmpty() {
checkForPadding()
emptyEmoji.text = getEmojiByUnicode(0x1F631)
empty.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
emptyText.visibility = if (adapter.itemCount == 0) View.VISIBLE else View.GONE
}
private fun getEmojiByUnicode(unicode: Int): String {
return String(Character.toChars(unicode))
}
public override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}
super.onPause()
}
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()
playlistSongsPresenter.detachView()
}
override fun showEmptyView() {
empty.visibility = View.VISIBLE
emptyText.visibility = View.VISIBLE
}
override fun songs(songs: List<Song>) {
adapter.swapDataSet(songs)
}
companion object {
var EXTRA_PLAYLIST = "extra_playlist"
}
}

View file

@ -1,159 +0,0 @@
package code.name.monkey.retromusic.activities
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
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.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?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pro_version)
setStatusbarColor(Color.TRANSPARENT)
setLightStatusbar(false)
setNavigationbarColor(Color.BLACK)
setLightNavigationBar(false)
toolbar.navigationIcon?.setTint(Color.WHITE)
toolbar.setNavigationOnClickListener { onBackPressed() }
restoreButton.isEnabled = false
purchaseButton.isEnabled = false
billingProcessor = BillingProcessor(this, BuildConfig.GOOGLE_PLAY_LICENSING_KEY, this)
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)
}
bannerContainer.backgroundTintList =
ColorStateList.valueOf(ThemeStore.accentColor(this))
}
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

@ -1,224 +0,0 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.app.Service
import android.content.ActivityNotFoundException
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 androidx.transition.TransitionManager
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsMusicServiceActivity
import code.name.monkey.retromusic.adapter.SearchAdapter
import code.name.monkey.retromusic.mvp.presenter.SearchPresenter
import code.name.monkey.retromusic.mvp.presenter.SearchView
import code.name.monkey.retromusic.util.RetroUtil
import com.google.android.material.textfield.TextInputEditText
import kotlinx.android.synthetic.main.activity_search.*
import java.util.*
import javax.inject.Inject
import kotlin.collections.ArrayList
class SearchActivity : AbsMusicServiceActivity(), OnQueryTextListener, TextWatcher, SearchView {
@Inject
lateinit var searchPresenter: SearchPresenter
private var searchAdapter: SearchAdapter? = null
private var query: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
App.musicComponent.inject(this)
searchPresenter.attachView(this)
setupRecyclerView()
setUpToolBar()
setupSearchView()
if (intent.getBooleanExtra(EXTRA_SHOW_MIC, false)) {
startMicSearch()
}
back.setOnClickListener { onBackPressed() }
voiceSearch.setOnClickListener { startMicSearch() }
clearText.setOnClickListener { searchView.clearText() }
searchContainer.backgroundTintList =
ColorStateList.valueOf(ATHUtil.resolveColor(this, R.attr.colorSurface))
keyboardPopup.setOnClickListener {
val inputManager = getSystemService(Service.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT)
}
keyboardPopup.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this,
ColorUtil.isColorLight(ThemeStore.accentColor(this))
)
).apply {
keyboardPopup.setTextColor(this)
keyboardPopup.iconTint = this
}
if (savedInstanceState != null) {
query = savedInstanceState.getString(QUERY)
}
}
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()
} else if (dy < 0) {
keyboardPopup.extend()
}
}
})
}
private fun setupSearchView() {
searchView.addTextChangedListener(this)
}
override fun onDestroy() {
super.onDestroy()
searchPresenter.detachView()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(QUERY, query)
}
private fun setUpToolBar() {
title = null
}
private fun search(query: String) {
this.query = query
TransitionManager.beginDelayedTransition(appBarLayout)
voiceSearch.visibility = if (query.isNotEmpty()) View.GONE else View.VISIBLE
clearText.visibility = if (query.isNotEmpty()) View.VISIBLE else View.GONE
searchPresenter.search(query)
}
override fun onMediaStoreChanged() {
super.onMediaStoreChanged()
query?.let { search(it) }
}
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 showEmptyView() {
searchAdapter?.swapDataSet(ArrayList())
}
override fun showData(data: MutableList<Any>) {
searchAdapter?.swapDataSet(data)
}
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: ArrayList<String>? =
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
query = result?.get(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 EXTRA_SHOW_MIC = "extra_show_mic"
const val QUERY: String = "query"
private const val REQ_CODE_SPEECH_INPUT = 9002
}
}
fun TextInputEditText.clearText() {
text = null
}

View file

@ -1,91 +0,0 @@
package code.name.monkey.retromusic.activities
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.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.fragments.settings.MainSettingsFragment
import com.afollestad.materialdialogs.color.ColorChooserDialog
import kotlinx.android.synthetic.main.activity_settings.*
class SettingsActivity : AbsBaseActivity(), ColorChooserDialog.ColorCallback {
private val fragmentManager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
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() {
setTitle(R.string.action_settings)
applyToolbar(toolbar)
}
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
)
fragmentTransaction.replace(R.id.contentFrame, fragment, fragment.tag)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
setTitle(titleName)
}
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount == 0) {
super.onBackPressed()
} else {
setTitle(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)
}
companion object {
const val TAG: String = "SettingsActivity"
}
override fun onColorSelection(dialog: ColorChooserDialog, selectedColor: Int) {
when (dialog.title) {
R.string.accent_color -> {
ThemeStore.editTheme(this).accentColor(selectedColor).commit()
if (VersionUtils.hasNougatMR())
DynamicShortcutManager(this).updateDynamicShortcuts()
}
}
recreate()
}
override fun onColorChooserDismissed(dialog: ColorChooserDialog) {
}
}

View file

@ -1,126 +0,0 @@
/*
* Copyright (c) 2020 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore.Images.Media
import android.view.MenuItem
import androidx.core.view.drawToBitmap
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.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.Share
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_share_instagram.*
/**
* Created by hemanths on 2020-02-02.
*/
class ShareInstagramStory : AbsBaseActivity() {
companion object {
const val EXTRA_SONG = "extra_song"
}
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?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_share_instagram)
setStatusbarColor(Color.TRANSPARENT)
setNavigationbarColor(Color.BLACK)
toolbar.setBackgroundColor(Color.TRANSPARENT)
setSupportActionBar(toolbar)
val song = intent.extras?.getParcelable<Song>(EXTRA_SONG)
song?.let { songFinal ->
SongGlideRequest.Builder.from(Glide.with(this), songFinal)
.checkIgnoreMediaStore(this@ShareInstagramStory)
.generatePalette(this@ShareInstagramStory)
.build()
.into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
val isColorLight = ColorUtil.isColorLight(colors.backgroundColor)
setColors(isColorLight, colors.backgroundColor)
}
})
shareTitle.text = songFinal.title
shareText.text = songFinal.artistName
shareButton.setOnClickListener {
val path: String = Media.insertImage(
contentResolver,
mainContent.drawToBitmap(Bitmap.Config.ARGB_8888),
"Design", null
)
val uri = Uri.parse(path)
Share.shareStoryToSocial(
this@ShareInstagramStory,
uri
)
}
}
shareButton.setTextColor(
MaterialValueHelper.getPrimaryTextColor(
this,
ColorUtil.isColorLight(ThemeStore.accentColor(this))
)
)
shareButton.backgroundTintList = ColorStateList.valueOf(ThemeStore.accentColor(this))
}
private fun setColors(colorLight: Boolean, color: Int) {
setLightStatusbar(colorLight)
toolbar.setTitleTextColor(
MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory,
colorLight
)
)
toolbar.navigationIcon?.setTintList(
ColorStateList.valueOf(
MaterialValueHelper.getPrimaryTextColor(
this@ShareInstagramStory,
colorLight
)
)
)
mainContent.background =
GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
intArrayOf(color, Color.BLACK)
)
}
}

View file

@ -1,252 +0,0 @@
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.appcompat.widget.AppCompatImageView
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.extensions.textColorPrimary
import code.name.monkey.retromusic.extensions.textColorSecondary
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_about.toolbar
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() {
val toolbarColor = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(toolbarColor)
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
}
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() {
billingProcessor?.release()
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
7 -> R.drawable.ic_food_croissant_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 \uD83C\uDFB5)", "")
.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 donationsDialog.textColorPrimary()
val contentTextColor =
if (purchased) titleTextColor else donationsDialog.textColorSecondary()
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: AppCompatImageView = 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

@ -1,218 +0,0 @@
package code.name.monkey.retromusic.activities
import android.app.Activity
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.view.MenuItem
import android.widget.Toast
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
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.extensions.accentColor
import code.name.monkey.retromusic.extensions.applyToolbar
import code.name.monkey.retromusic.glide.ProfileBannerGlideRequest
import code.name.monkey.retromusic.glide.UserProfileGlideRequest
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.constant.ImageProvider
import kotlinx.android.synthetic.main.activity_user_info.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class UserInfoActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_info)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
applyToolbar(toolbar)
MaterialUtil.setTint(nameContainer, false)
name.setText(PreferenceUtil.userName)
userImage.setOnClickListener {
pickNewPhoto()
}
bannerSelect.setOnClickListener {
selectBannerImage()
}
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
}
PreferenceUtil.userName = nameString
setResult(Activity.RESULT_OK)
finish()
}
val textColor =
MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(accentColor()))
next.backgroundTintList = ColorStateList.valueOf(accentColor())
next.iconTint = ColorStateList.valueOf(textColor)
next.setTextColor(textColor)
loadProfile()
}
private fun loadProfile() {
bannerImage?.let {
ProfileBannerGlideRequest.Builder.from(
Glide.with(this),
ProfileBannerGlideRequest.getBannerModel()
).build().into(it)
}
UserProfileGlideRequest.Builder.from(
Glide.with(this),
UserProfileGlideRequest.getUserModel()
).build().into(userImage)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
private fun selectBannerImage() {
ImagePicker.with(this)
.compress(1440)
.provider(ImageProvider.GALLERY)
.crop(16f, 9f)
.start(PICK_BANNER_REQUEST)
}
private fun pickNewPhoto() {
ImagePicker.with(this)
.provider(ImageProvider.GALLERY)
.cropSquare()
.compress(1440)
.start(PICK_IMAGE_REQUEST)
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == PICK_IMAGE_REQUEST) {
val fileUri = data?.data
fileUri?.let { setAndSaveUserImage(it) }
} else if (resultCode == Activity.RESULT_OK && requestCode == PICK_BANNER_REQUEST) {
val fileUri = data?.data
fileUri?.let { setAndSaveBannerImage(it) }
} else if (resultCode == ImagePicker.RESULT_ERROR) {
Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Task Cancelled", Toast.LENGTH_SHORT).show()
}
}
private fun setAndSaveBannerImage(fileUri: Uri) {
Glide.with(this)
.load(fileUri)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
isFromMemoryCache: Boolean,
isFirstResource: Boolean
): Boolean {
resource?.let { saveImage(it, USER_BANNER) }
return false
}
})
.into(bannerImage)
}
private fun saveImage(bitmap: Bitmap, fileName: String) {
CoroutineScope(Dispatchers.IO).launch() {
val appDir = applicationContext.filesDir
val file = File(appDir, fileName)
var successful = false
try {
val os = BufferedOutputStream(FileOutputStream(file))
successful = ImageUtil.resizeBitmap(bitmap, 2048)
.compress(Bitmap.CompressFormat.WEBP, 100, os)
withContext(Dispatchers.IO) { os.close() }
} catch (e: IOException) {
e.printStackTrace()
}
if (successful) {
withContext(Dispatchers.Main) {
Toast.makeText(this@UserInfoActivity, "Updated", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun setAndSaveUserImage(fileUri: Uri) {
Glide.with(this)
.load(fileUri)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(object : RequestListener<Any, Bitmap> {
override fun onException(
e: java.lang.Exception?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
isFromMemoryCache: Boolean,
isFirstResource: Boolean
): Boolean {
resource?.let { saveImage(it, USER_PROFILE) }
return false
}
})
.into(userImage)
}
companion object {
private const val PICK_IMAGE_REQUEST = 9002
private const val PICK_BANNER_REQUEST = 9004
}
}

View file

@ -1,90 +0,0 @@
package code.name.monkey.retromusic.activities;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.ATHUtil;
import code.name.monkey.appthemehelper.util.ColorUtil;
import code.name.monkey.appthemehelper.util.MaterialValueHelper;
import 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 {
private static String colorToCSS(int color) {
return String.format(Locale.getDefault(), "rgba(%d, %d, %d, %d)", Color.red(color), Color.green(color),
Color.blue(color), Color.alpha(color)); // on API 29, WebView doesn't load with hex colors
}
private static void setChangelogRead(@NonNull Context context) {
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
int currentVersion = pInfo.versionCode;
PreferenceUtil.INSTANCE.setLastVersion(currentVersion);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setDrawUnderStatusBar();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_whats_new);
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
WebView webView = findViewById(R.id.webView);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setBackgroundColor(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface));
toolbar.setNavigationOnClickListener(v -> onBackPressed());
ToolbarContentTintHelper.colorBackButton(toolbar);
try {
StringBuilder buf = new StringBuilder();
InputStream json = getAssets().open("retro-changelog.html");
BufferedReader in = new BufferedReader(new InputStreamReader(json, StandardCharsets.UTF_8));
String str;
while ((str = in.readLine()) != null) {
buf.append(str);
}
in.close();
// Inject color values for WebView body background and links
final boolean isDark = ATHUtil.INSTANCE.isWindowBackgroundDark(this);
final int accentColor = ThemeStore.Companion.accentColor(this);
final String backgroundColor = colorToCSS(ATHUtil.INSTANCE.resolveColor(this, R.attr.colorSurface, Color.parseColor(isDark ? "#424242" : "#ffffff")));
final String contentColor = colorToCSS(Color.parseColor(isDark ? "#ffffff" : "#000000"));
final String textColor = colorToCSS(Color.parseColor(isDark ? "#60FFFFFF" : "#80000000"));
final String accentColorString = colorToCSS(ThemeStore.Companion.accentColor(this));
final String accentTextColor = colorToCSS(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.INSTANCE.isColorLight(accentColor)));
final String changeLog = buf.toString()
.replace("{style-placeholder}", String.format("body { background-color: %s; color: %s; } li {color: %s;} .colorHeader {background-color: %s; color: %s;} .tag {color: %s;}", backgroundColor, contentColor, textColor, accentColorString, accentTextColor, accentColorString))
.replace("{link-color}", colorToCSS(ThemeStore.Companion.accentColor(this)))
.replace("{link-color-active}", colorToCSS(ColorUtil.INSTANCE.lightenColor(ThemeStore.Companion.accentColor(this))));
webView.loadData(changeLog, "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

@ -1,401 +0,0 @@
package code.name.monkey.retromusic.activities.albums
import android.app.ActivityOptions
import android.content.Intent
import android.os.Bundle
import android.transition.Slide
import android.view.Menu
import android.view.MenuItem
import android.view.SubMenu
import android.view.View
import android.widget.ImageView
import androidx.core.app.ActivityCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
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.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.ArtistGlideRequest
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.interfaces.CabHolder
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.presenter.AlbumDetailsView
import code.name.monkey.retromusic.rest.model.LastFmAlbum
import code.name.monkey.retromusic.util.*
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_album.*
import kotlinx.android.synthetic.main.activity_album_content.*
import java.util.*
import android.util.Pair as UtilPair
class AlbumDetailsActivity : AbsSlidingMusicPanelActivity(), AlbumDetailsView, CabHolder {
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
if (it.isActive) it.finish()
}
cab = MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
)
.start(callback)
return cab as MaterialCab
}
private lateinit var viewModel: AlbumDetailsViewModel
private lateinit var simpleSongAdapter: SimpleSongAdapter
private lateinit var album: Album
private lateinit var artistImage: ImageView
private var cab: MaterialCab? = null
private val savedSortOrder: String
get() = PreferenceUtil.albumDetailSongSortOrder
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_album)
}
private fun windowEnterTransition() {
val slide = Slide()
slide.excludeTarget(R.id.appBarLayout, true)
slide.excludeTarget(R.id.status_bar, true)
slide.excludeTarget(android.R.id.statusBarBackground, true)
slide.excludeTarget(android.R.id.navigationBarBackground, true)
window.enterTransition = slide
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setBottomBarVisibility(View.GONE)
window.sharedElementsUseOverlay = true
windowEnterTransition()
val albumId = extraNotNull<Int>(EXTRA_ALBUM_ID).value
ActivityCompat.postponeEnterTransition(this)
val viewModelFactory = AlbumDetailsViewModelFactory(application, albumId)
viewModel = ViewModelProvider(this, viewModelFactory).get(AlbumDetailsViewModel::class.java)
addMusicServiceEventListener(viewModel)
viewModel.getAlbum().observe(this, androidx.lifecycle.Observer {
ActivityCompat.startPostponedEnterTransition(this@AlbumDetailsActivity)
album(it)
})
viewModel.getArtist().observe(this, androidx.lifecycle.Observer {
loadArtistImage(it)
})
viewModel.getAlbumInfo().observe(this, androidx.lifecycle.Observer {
aboutAlbum(it)
})
setupRecyclerView()
artistImage = findViewById(R.id.artistImage)
artistImage.setOnClickListener {
val artistPairs = ActivityOptions.makeSceneTransitionAnimation(
this,
UtilPair.create(
artistImage,
getString(R.string.transition_artist_image)
)
)
NavigationUtil.goToArtistOptions(this, album.artistId, artistPairs)
}
playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(album.songs!!, 0, true) }
}
shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(album.songs!!, true) }
}
aboutAlbumText.setOnClickListener {
if (aboutAlbumText.maxLines == 4) {
aboutAlbumText.maxLines = Integer.MAX_VALUE
} else {
aboutAlbumText.maxLines = 4
}
}
}
private fun setupRecyclerView() {
simpleSongAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song, this)
recyclerView.apply {
layoutManager = LinearLayoutManager(this@AlbumDetailsActivity)
itemAnimator = DefaultItemAnimator()
isNestedScrollingEnabled = false
adapter = simpleSongAdapter
}
}
override fun complete() {
ActivityCompat.startPostponedEnterTransition(this)
}
override fun album(album: Album) {
complete()
if (album.songs!!.isEmpty()) {
finish()
return
}
this.album = album
albumTitle.text = album.title
val songText =
resources.getQuantityString(
R.plurals.albumSongs,
album.songCount,
album.songCount
)
songTitle.text = songText
if (MusicUtil.getYearString(album.year) == "-") {
albumText.text = String.format(
"%s • %s",
album.artistName,
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
)
} else {
albumText.text = String.format(
"%s • %s • %s",
album.artistName,
MusicUtil.getYearString(album.year),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(album.songs))
)
}
loadAlbumCover()
simpleSongAdapter.swapDataSet(album.songs)
viewModel.loadArtist(album.artistId)
viewModel.loadAlbumInfo(album)
}
override fun moreAlbums(albums: List<Album>) {
moreTitle.show()
moreRecyclerView.show()
moreTitle.text = String.format(getString(R.string.label_more_from), album.artistName)
val albumAdapter = HorizontalAlbumAdapter(this, albums, null)
moreRecyclerView.layoutManager = GridLayoutManager(
this,
1,
GridLayoutManager.HORIZONTAL,
false
)
moreRecyclerView.adapter = albumAdapter
}
override fun aboutAlbum(lastFmAlbum: LastFmAlbum) {
if (lastFmAlbum.album != null) {
if (lastFmAlbum.album.wiki != null) {
aboutAlbumText.show()
aboutAlbumTitle.show()
aboutAlbumTitle.text =
String.format(getString(R.string.about_album_label), lastFmAlbum.album.name)
aboutAlbumText.text = lastFmAlbum.album.wiki.content
}
if (lastFmAlbum.album.listeners.isNotEmpty()) {
listeners.show()
listenersLabel.show()
scrobbles.show()
scrobblesLabel.show()
listeners.text = RetroUtil.formatValue(lastFmAlbum.album.listeners.toFloat())
scrobbles.text = RetroUtil.formatValue(lastFmAlbum.album.playcount.toFloat())
}
}
}
override fun loadArtistImage(artist: Artist) {
ArtistGlideRequest.Builder.from(Glide.with(this), artist)
.generatePalette(this)
.build()
.dontAnimate()
.dontTransform()
.into(object : RetroMusicColoredTarget(artistImage) {
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
}
private fun loadAlbumCover() {
AlbumGlideRequest.Builder.from(Glide.with(this), album.safeGetFirstSong())
.checkIgnoreMediaStore(this)
.ignoreMediaStore(PreferenceUtil.isIgnoreMediaStoreArtwork)
.generatePalette(this)
.build()
.dontAnimate()
.dontTransform()
.into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors)
}
})
}
private fun setColors(color: MediaNotificationProcessor) {
val buttonColor = if (PreferenceUtil.isAdaptiveColor)
color.backgroundColor.ripAlpha()
else
ATHUtil.resolveColor(this, R.attr.colorSurface)
MaterialUtil.setTint(button = shuffleAction, color = buttonColor)
MaterialUtil.setTint(button = playAction, color = buttonColor)
toolbar.setBackgroundColor(surfaceColor())
setSupportActionBar(toolbar)
supportActionBar?.title = null
}
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)
ToolbarContentTintHelper.handleOnCreateOptionsMenu(
this,
toolbar,
menu,
getToolbarBackgroundColor(toolbar)
)
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)
val options = ActivityOptions.makeSceneTransitionAnimation(
this,
albumCoverContainer,
"${getString(R.string.transition_album_art)}_${album.id}"
)
startActivityForResult(
intent,
TAG_EDITOR_REQUEST, options.toBundle()
)
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.albumDetailSongSortOrder = sortOrder
when (sortOrder) {
AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs?.sortWith(Comparator { o1, o2 ->
o1.trackNumber.compareTo(
o2.trackNumber
)
})
AlbumSongSortOrder.SONG_A_Z -> album.songs?.sortWith(Comparator { o1, o2 ->
o1.title.compareTo(
o2.title
)
})
AlbumSongSortOrder.SONG_Z_A -> album.songs?.sortWith(Comparator { o1, o2 ->
o2.title.compareTo(
o1.title
)
})
AlbumSongSortOrder.SONG_DURATION -> album.songs?.sortWith(Comparator { o1, o2 ->
o1.duration.compareTo(
o2.duration
)
})
}
album.songs?.let { simpleSongAdapter.swapDataSet(it) }
}
override fun onBackPressed() {
if (cab != null && cab!!.isActive) {
cab?.finish()
} else {
super.onBackPressed()
}
}
override fun onDestroy() {
super.onDestroy()
removeMusicServiceEventListener(viewModel)
}
companion object {
const val EXTRA_ALBUM_ID = "extra_album_id"
private const val TAG_EDITOR_REQUEST = 2001
}
}

View file

@ -1,66 +0,0 @@
package code.name.monkey.retromusic.activities.albums
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.rest.model.LastFmAlbum
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class AlbumDetailsViewModel(
application: Application,
private val albumId: Int
) : AndroidViewModel(application), MusicServiceEventListener {
private val _repository = RepositoryImpl(application.applicationContext)
private val _album = MutableLiveData<Album>()
private val _artist = MutableLiveData<Artist>()
private val _lastFmAlbum = MutableLiveData<LastFmAlbum>()
fun getAlbum(): LiveData<Album> = _album
fun getArtist(): LiveData<Artist> = _artist
fun getAlbumInfo(): LiveData<LastFmAlbum> = _lastFmAlbum
init {
loadAlbumDetails()
}
private fun loadAlbumDetails() = viewModelScope.launch {
val album = loadAlbumAsync.await() ?: throw NullPointerException("Album couldn't found")
_album.postValue(album)
}
fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) {
val lastFmAlbum = _repository.albumInfo(album.artistName ?: "-", album.title ?: "-")
_lastFmAlbum.postValue(lastFmAlbum)
}
fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) {
val artist = _repository.artistById(artistId)
_artist.postValue(artist)
}
private val loadAlbumAsync: Deferred<Album?>
get() = viewModelScope.async(Dispatchers.IO) {
_repository.albumById(albumId)
}
override fun onMediaStoreChanged() {
loadAlbumDetails()
}
override fun onServiceConnected() {}
override fun onServiceDisconnected() {}
override fun onQueueChanged() {}
override fun onPlayingMetaChanged() {}
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
}

View file

@ -1,19 +0,0 @@
package code.name.monkey.retromusic.activities.albums
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class AlbumDetailsViewModelFactory(
private val application: Application,
private val albumId: Int
) :
ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(AlbumDetailsViewModel::class.java)) {
AlbumDetailsViewModel(application, albumId) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}

View file

@ -1,333 +0,0 @@
package code.name.monkey.retromusic.activities.artists
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.text.Spanned
import android.transition.Slide
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.text.HtmlCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.ripAlpha
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.mvp.presenter.ArtistDetailsView
import code.name.monkey.retromusic.rest.model.LastFmArtist
import code.name.monkey.retromusic.util.*
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_artist_content.*
import kotlinx.android.synthetic.main.activity_artist_details.*
import java.util.*
import kotlin.collections.ArrayList
class ArtistDetailActivity : AbsSlidingMusicPanelActivity(), ArtistDetailsView, CabHolder {
override fun openCab(menuRes: Int, callback: MaterialCab.Callback): MaterialCab {
cab?.let {
if (it.isActive) it.finish()
}
cab = MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(
RetroColorUtil.shiftBackgroundColorForLightText(
ATHUtil.resolveColor(
this,
R.attr.colorSurface
)
)
)
.start(callback)
return cab as MaterialCab
}
private var cab: MaterialCab? = null
private var biography: Spanned? = null
private lateinit var artist: Artist
private lateinit var songAdapter: SimpleSongAdapter
private lateinit var albumAdapter: HorizontalAlbumAdapter
private var forceDownload: Boolean = false
private lateinit var viewModel: ArtistDetailsViewModel
override fun createContentView(): View {
return wrapSlidingMusicPanel(R.layout.activity_artist_details)
}
private fun windowEnterTransition() {
val slide = Slide()
slide.excludeTarget(R.id.appBarLayout, true)
slide.excludeTarget(R.id.status_bar, true)
slide.excludeTarget(android.R.id.statusBarBackground, true)
slide.excludeTarget(android.R.id.navigationBarBackground, true)
window.enterTransition = slide
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
setLightNavigationBar(true)
setBottomBarVisibility(View.GONE)
window.sharedElementsUseOverlay = true
windowEnterTransition()
val artistId = extraNotNull<Int>(EXTRA_ARTIST_ID).value
val viewModelFactory = ArtistDetailsViewModelFactory(application, artistId)
viewModel =
ViewModelProvider(this, viewModelFactory).get(ArtistDetailsViewModel::class.java)
addMusicServiceEventListener(viewModel)
viewModel.getArtist().observe(this, androidx.lifecycle.Observer {
ActivityCompat.startPostponedEnterTransition(this@ArtistDetailActivity)
artist(it)
})
viewModel.getArtistInfo().observe(this, androidx.lifecycle.Observer {
artistInfo(it)
})
ActivityCompat.postponeEnterTransition(this)
setupRecyclerView()
playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
}
shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
}
biographyText.setOnClickListener {
if (biographyText.maxLines == 4) {
biographyText.maxLines = Integer.MAX_VALUE
} else {
biographyText.maxLines = 4
}
}
}
private fun setupRecyclerView() {
albumAdapter = HorizontalAlbumAdapter(this, ArrayList(), null)
albumRecyclerView.apply {
itemAnimator = DefaultItemAnimator()
layoutManager = GridLayoutManager(this.context, 1, GridLayoutManager.HORIZONTAL, false)
adapter = albumAdapter
}
songAdapter = SimpleSongAdapter(this, ArrayList(), R.layout.item_song, this)
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) {
data?.data?.let {
CustomArtistImageUtil.getInstance(this).setCustomArtistImage(artist, it)
}
}
else -> if (resultCode == Activity.RESULT_OK) {
//reload()
}
}
}
override fun showEmptyView() {
}
override fun complete() {
ActivityCompat.startPostponedEnterTransition(this)
}
override fun artist(artist: Artist) {
complete()
if (artist.songCount <= 0) {
finish()
}
this.artist = artist
loadArtistImage()
if (RetroUtil.isAllowedToDownloadMetadata(this)) {
loadBiography(artist.name)
}
artistTitle.text = artist.name
text.text = String.format(
"%s • %s",
MusicUtil.getArtistInfoString(this, artist),
MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(artist.songs))
)
val songText =
resources.getQuantityString(
R.plurals.albumSongs,
artist.songCount,
artist.songCount
)
val albumText =
resources.getQuantityString(
R.plurals.albums,
artist.songCount,
artist.songCount
)
songTitle.text = songText
albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs)
albumAdapter.swapDataSet(artist.albums!!)
}
private fun loadBiography(
name: String,
lang: String? = Locale.getDefault().language
) {
biography = null
this.lang = lang
viewModel.loadBiography(name, lang, null)
}
override fun artistInfo(lastFmArtist: LastFmArtist?) {
if (lastFmArtist != null && lastFmArtist.artist != null) {
val bioContent = lastFmArtist.artist.bio.content
if (bioContent != null && bioContent.trim { it <= ' ' }.isNotEmpty()) {
biographyText.visibility = View.VISIBLE
biographyTitle.visibility = View.VISIBLE
biography = HtmlCompat.fromHtml(bioContent, HtmlCompat.FROM_HTML_MODE_LEGACY)
biographyText.text = biography
if (lastFmArtist.artist.stats.listeners.isNotEmpty()) {
listeners.show()
listenersLabel.show()
scrobbles.show()
scrobblesLabel.show()
listeners.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.listeners.toFloat())
scrobbles.text =
RetroUtil.formatValue(lastFmArtist.artist.stats.playcount.toFloat())
}
}
}
// If the "lang" parameter is set and no biography is given, retry with default language
if (biography == null && lang != null) {
loadBiography(artist.name, null)
}
}
private var lang: String? = null
private fun loadArtistImage() {
ArtistGlideRequest.Builder.from(Glide.with(this), artist).generatePalette(this).build()
.dontAnimate().into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors)
}
})
}
private fun setColors(color: MediaNotificationProcessor) {
val buttonColor = if (PreferenceUtil.isAdaptiveColor)
color.backgroundColor.ripAlpha()
else
ATHUtil.resolveColor(this, R.attr.colorSurface)
MaterialUtil.setTint(button = shuffleAction, color = buttonColor)
MaterialUtil.setTint(button = playAction, color = buttonColor)
toolbar.setBackgroundColor(surfaceColor())
setSupportActionBar(toolbar)
supportActionBar?.title = null
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return handleSortOrderMenuItem(item)
}
private fun handleSortOrderMenuItem(item: MenuItem): Boolean {
val songs = artist.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 onBackPressed() {
if (cab != null && cab!!.isActive) {
cab?.finish()
} else {
super.onBackPressed()
}
}
override fun onDestroy() {
super.onDestroy()
removeMusicServiceEventListener(viewModel)
}
companion object {
const val EXTRA_ARTIST_ID = "extra_artist_id"
const val REQUEST_CODE_SELECT_IMAGE = 9003
}
}

View file

@ -1,59 +0,0 @@
package code.name.monkey.retromusic.activities.artists
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.rest.model.LastFmArtist
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class ArtistDetailsViewModel(
application: Application,
private val artistId: Int
) : AndroidViewModel(application), MusicServiceEventListener {
private val loadArtistDetailsAsync: Deferred<Artist?>
get() = viewModelScope.async(Dispatchers.IO) {
_repository.artistById(artistId)
}
private val _repository = RepositoryImpl(application.applicationContext)
private val _artist = MutableLiveData<Artist>()
private val _lastFmArtist = MutableLiveData<LastFmArtist>()
fun getArtist(): LiveData<Artist> = _artist
fun getArtistInfo(): LiveData<LastFmArtist> = _lastFmArtist
init {
loadArtistDetails()
}
private fun loadArtistDetails() = viewModelScope.launch {
val artist =
loadArtistDetailsAsync.await() ?: throw NullPointerException("Album couldn't found")
_artist.postValue(artist)
}
fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch {
val info = _repository.artistInfo(name, lang, cache)
_lastFmArtist.postValue(info)
}
override fun onMediaStoreChanged() {
loadArtistDetails()
}
override fun onServiceConnected() {}
override fun onServiceDisconnected() {}
override fun onQueueChanged() {}
override fun onPlayingMetaChanged() {}
override fun onPlayStateChanged() {}
override fun onRepeatModeChanged() {}
override fun onShuffleModeChanged() {}
}

View file

@ -1,19 +0,0 @@
package code.name.monkey.retromusic.activities.artists
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class ArtistDetailsViewModelFactory(
private val application: Application,
private val artistId: Int
) :
ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(ArtistDetailsViewModel::class.java)) {
ArtistDetailsViewModel(application, artistId) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}

View file

@ -1,147 +0,0 @@
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 code.name.monkey.retromusic.R
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(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
println(hasPermissions)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == KeyEvent.KEYCODE_MENU && event.action == KeyEvent.ACTION_UP) {
showOverflowMenu()
return true
}
return super.dispatchKeyEvent(event)
}
private 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

@ -1,170 +0,0 @@
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.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.service.MusicService.*
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)
filter.addAction(FAVORITE_STATE_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)
println("sendBroadcast $hasPermissions")
}
override fun getPermissionsToRequest(): Array<String> {
return arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.BLUETOOTH
)
}
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) {
FAVORITE_STATE_CHANGED, 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

@ -1,388 +0,0 @@
package code.name.monkey.retromusic.activities.base
import android.animation.ValueAnimator
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
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.circle.CirclePlayerFragment
import code.name.monkey.retromusic.fragments.player.classic.ClassicPlayerFragment
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.gradient.GradientPlayerFragment
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.peak.PeakPlayerFragment
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.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.DensityUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.views.BottomNavigationBarTinted
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity(),
AbsPlayerFragment.Callbacks {
companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
}
private lateinit var behavior: RetroBottomSheetBehavior<FrameLayout>
private var miniPlayerFragment: MiniPlayerFragment? = null
private var playerFragment: AbsPlayerFragment? = null
private var cps: 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
private lateinit var shapeDrawable: MaterialShapeDrawable
private val panelState: Int
get() = behavior.state
private val bottomSheetCallbackList = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
setMiniPlayerAlphaProgress(slideOffset)
dimBackground.show()
dimBackground.alpha = slideOffset
shapeDrawable.interpolation = 1 - slideOffset
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> {
onPanelExpanded()
}
BottomSheetBehavior.STATE_COLLAPSED -> {
onPanelCollapsed()
dimBackground.hide()
}
else -> {
}
}
}
}
fun getBottomSheetBehavior() = behavior
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(createContentView())
chooseFragmentForTheme()
setupSlidingUpPanel()
behavior = BottomSheetBehavior.from(slidingPanel) as RetroBottomSheetBehavior
val themeColor = ATHUtil.resolveColor(this, android.R.attr.windowBackground, Color.GRAY)
dimBackground.setBackgroundColor(ColorUtil.withAlpha(themeColor, 0.5f))
shapeDrawable = MaterialShapeDrawable(
ShapeAppearanceModel.builder(
this,
R.style.ClassicThemeOverLay,
0
).build()
)
slidingPanel.background = shapeDrawable
}
override fun onResume() {
super.onResume()
if (cps != PreferenceUtil.nowPlayingScreen) {
postRecreate()
}
behavior.addBottomSheetCallback(bottomSheetCallbackList)
if (behavior.state == BottomSheetBehavior.STATE_EXPANDED) {
setMiniPlayerAlphaProgress(1f)
}
}
override fun onDestroy() {
super.onDestroy()
behavior.removeBottomSheetCallback(bottomSheetCallbackList)
if (navigationBarColorAnimator != null) navigationBarColorAnimator?.cancel() // just in case
}
protected fun wrapSlidingMusicPanel(@LayoutRes resId: Int): View {
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
}
private fun collapsePanel() {
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
fun expandPanel() {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
setMiniPlayerAlphaProgress(1f)
}
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() {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
slidingPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
if (cps != Peak) {
val params = slidingPanel.layoutParams as ViewGroup.LayoutParams
params.height = ViewGroup.LayoutParams.MATCH_PARENT
slidingPanel.layoutParams = params
}
when (panelState) {
BottomSheetBehavior.STATE_EXPANDED -> onPanelExpanded()
BottomSheetBehavior.STATE_COLLAPSED -> onPanelCollapsed()
else -> playerFragment!!.onHide()
}
}
})
}
fun getBottomNavigationView(): BottomNavigationBarTinted {
return bottomNavigationView
}
fun setBottomBarVisibility(visible: Int) {
bottomNavigationView.visibility = visible
hideBottomBar(MusicPlayerRemote.playingQueue.isEmpty())
}
private 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) {
behavior.isHideable = true
behavior.peekHeight = 0
bottomNavigationView.elevation = DensityUtil.dip2px(this, 10f).toFloat()
collapsePanel()
} else {
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
slidingPanel.elevation = DensityUtil.dip2px(this, 10f).toFloat()
bottomNavigationView.elevation = DensityUtil.dip2px(this, 10f).toFloat()
behavior.isHideable = false
behavior.peekHeight =
if (bottomNavigationView.visibility == View.VISIBLE) {
heightOfBarWithTabs
} else {
heightOfBar
}
}
}
}
private fun chooseFragmentForTheme() {
cps = PreferenceUtil.nowPlayingScreen
val fragment: Fragment = when (cps) {
Blur -> BlurPlayerFragment()
Adaptive -> AdaptiveFragment()
Normal -> PlayerFragment()
Card -> CardFragment()
BlurCard -> CardBlurFragment()
Fit -> FitFragment()
Flat -> FlatPlayerFragment()
Full -> FullPlayerFragment()
Plain -> PlainPlayerFragment()
Simple -> SimplePlayerFragment()
Material -> MaterialFragment()
Color -> ColorFragment()
Tiny -> TinyPlayerFragment()
Peak -> PeakPlayerFragment()
Circle -> CirclePlayerFragment()
Classic -> ClassicPlayerFragment()
Gradient -> GradientPlayerFragment()
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.isNotEmpty()) {
slidingPanel.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
slidingPanel.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 (behavior.peekHeight != 0 && playerFragment!!.onBackPressed()) return true
if (panelState == BottomSheetBehavior.STATE_EXPANDED) {
collapsePanel()
return true
}
return false
}
override fun onPaletteColorChanged() {
if (panelState == BottomSheetBehavior.STATE_EXPANDED) {
val paletteColor = playerFragment!!.paletteColor
super.setTaskDescriptionColor(paletteColor)
val isColorLight = ColorUtil.isColorLight(paletteColor)
if (PreferenceUtil.isAdaptiveColor && (cps == Normal || cps == Flat)) {
super.setLightNavigationBar(true)
super.setLightStatusbar(isColorLight)
} else if (cps == Card || cps == Blur || cps == BlurCard) {
super.setLightStatusbar(false)
super.setLightNavigationBar(true)
super.setNavigationbarColor(Color.BLACK)
} else if (cps == Color || cps == Tiny || cps == Gradient) {
super.setNavigationbarColor(paletteColor)
super.setLightNavigationBar(isColorLight)
super.setLightStatusbar(isColorLight)
} else if (cps == Full) {
super.setNavigationbarColor(paletteColor)
super.setLightNavigationBar(isColorLight)
super.setLightStatusbar(false)
} else if (cps == Classic) {
super.setLightStatusbar(false)
} else if (cps == Fit) {
super.setLightStatusbar(false)
} else {
super.setLightStatusbar(
ColorUtil.isColorLight(
ATHUtil.resolveColor(
this,
android.R.attr.windowBackground
)
)
)
super.setLightNavigationBar(true)
}
}
}
override fun setLightStatusbar(enabled: Boolean) {
lightStatusBar = enabled
if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
super.setLightStatusbar(enabled)
}
}
override fun setLightNavigationBar(enabled: Boolean) {
lightNavigationBar = enabled
if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
super.setLightNavigationBar(enabled)
}
}
override fun setNavigationbarColor(color: Int) {
navigationBarColor = color
if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
if (navigationBarColorAnimator != null) navigationBarColorAnimator!!.cancel()
super.setNavigationbarColor(color)
}
}
override fun setTaskDescriptionColor(color: Int) {
taskColor = color
if (panelState == BottomSheetBehavior.STATE_COLLAPSED) {
super.setTaskDescriptionColor(color)
}
}
fun updateTabs() {
bottomNavigationView.menu.clear()
val currentTabs: List<CategoryInfo> = PreferenceUtil.libraryCategory
for (tab in currentTabs) {
if (tab.visible) {
val menu = tab.category
bottomNavigationView.menu.add(0, menu.id, 0, menu.stringRes).setIcon(menu.icon)
}
}
if (bottomNavigationView.menu.size() == 1) {
bottomNavigationView.hide()
}
}
}

View file

@ -1,207 +0,0 @@
package code.name.monkey.retromusic.activities.base
import android.content.Context
import android.graphics.Color
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.appcompat.app.AppCompatDelegate.setDefaultNightMode
import code.name.monkey.appthemehelper.ATH
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialDialogsUtil
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.LanguageContextWrapper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.theme.ThemeManager
import java.util.*
abstract class AbsThemeActivity : ATHToolbarActivity(), Runnable {
private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
updateTheme()
hideStatusBar()
super.onCreate(savedInstanceState)
setImmersiveFullscreen()
registerSystemUiVisibility()
toggleScreenOn()
MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this)
}
private fun updateTheme() {
setTheme(ThemeManager.getThemeResValue(this))
setDefaultNightMode(ThemeManager.getNightMode(this))
}
private fun toggleScreenOn() {
if (PreferenceUtil.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.isFullScreenMode)
}
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
}
}
fun setDrawUnderStatusBar() {
RetroUtil.setAllowDrawUnderStatusBar(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) {
val statusBar = window.decorView.rootView.findViewById<View>(R.id.status_bar)
if (statusBar != null) {
when {
VersionUtils.hasMarshmallow() -> statusBar.setBackgroundColor(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(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
fun setStatusbarColorAuto() {
// we don't want to use statusbar color because we are doing the color darkening on our own to support KitKat
setStatusbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setLightStatusbarAuto(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setTaskDescriptionColor(@ColorInt color: Int) {
ATH.setTaskDescriptionColor(this, color)
}
fun setTaskDescriptionColorAuto() {
setTaskDescriptionColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
open fun setNavigationbarColor(color: Int) {
if (ThemeStore.coloredNavigationBar(this)) {
ATH.setNavigationbarColor(this, color)
} else {
ATH.setNavigationbarColor(this, Color.BLACK)
}
}
fun setNavigationbarColorAuto() {
setNavigationbarColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
}
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.isFullScreenMode) {
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)
}
override fun attachBaseContext(newBase: Context?) {
val code = PreferenceUtil.languageCode
if (code != "auto") {
super.attachBaseContext(LanguageContextWrapper.wrap(newBase, Locale(code)))
} else super.attachBaseContext(newBase)
}
}

View file

@ -1,329 +0,0 @@
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.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.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 code.name.monkey.retromusic.misc.DialogAsyncTask
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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?) {
setDrawUnderStatusBar()
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 = ATHUtil.resolveColor(this, R.attr.colorSurface)
toolbar.setBackgroundColor(primaryColor)
setSupportActionBar(toolbar)
ToolbarContentTintHelper.colorBackButton(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() }
MaterialUtil.setTint(inputLayoutTitle, false)
MaterialUtil.setTint(inputLayoutDescription, false)
MaterialUtil.setTint(inputLayoutUsername, false)
MaterialUtil.setTint(inputLayoutPassword, 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.setPrimaryClip(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 -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_wrong_credentials)
.setPositiveButton(android.R.string.ok, null)
.show()
RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_invalid_token)
.setPositiveButton(android.R.string.ok, null).show()
RESULT_ISSUES_NOT_ENABLED -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_issues_not_available)
.setPositiveButton(android.R.string.ok, null)
else -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_unknown)
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
.setNegativeButton(android.R.string.cancel) { _, _ -> { 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

@ -1,130 +0,0 @@
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 androidx.annotation.IntRange;
import java.util.Arrays;
import java.util.Locale;
import code.name.monkey.retromusic.util.PreferenceUtil;
public class DeviceInfo {
@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;
private final String baseTheme;
private final String brand = Build.BRAND;
private final String buildID = Build.DISPLAY;
private final String buildVersion = Build.VERSION.INCREMENTAL;
private final String device = Build.DEVICE;
private final String hardware = Build.HARDWARE;
private final boolean isAdaptive;
private final String manufacturer = Build.MANUFACTURER;
private final String model = Build.MODEL;
private final String nowPlayingTheme;
private final String product = Build.PRODUCT;
private final String releaseVersion = Build.VERSION.RELEASE;
@IntRange(from = 0)
private final int sdkVersion = Build.VERSION.SDK_INT;
private final int versionCode;
private final String versionName;
private final String selectedLang;
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.INSTANCE.getBaseTheme();
nowPlayingTheme = context.getString(PreferenceUtil.INSTANCE.getNowPlayingScreen().getTitleRes());
isAdaptive = PreferenceUtil.INSTANCE.isAdaptiveColor();
selectedLang = PreferenceUtil.INSTANCE.getLanguageCode();
}
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"
+ "<tr><td>Language</td><td>" + selectedLang + "</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 + "\n"
+ "System language: " + Locale.getDefault().toLanguageTag() + "\n"
+ "In-App Language: " + selectedLang;
}
}

View file

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

View file

@ -1,62 +0,0 @@
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

@ -1,41 +0,0 @@
package code.name.monkey.retromusic.activities.bugreport.model.github;
import android.text.TextUtils;
public class GithubLogin {
private final String apiToken;
private final String password;
private final String username;
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 getApiToken() {
return apiToken;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean shouldUseApiToken() {
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password);
}
}

View file

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

View file

@ -1,72 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.activities.saf;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.Nullable;
import com.heinrichreimersoftware.materialintro.app.IntroActivity;
import com.heinrichreimersoftware.materialintro.slide.SimpleSlide;
import code.name.monkey.retromusic.R;
/**
* Created by hemanths on 2019-07-31.
*/
public class SAFGuideActivity extends IntroActivity {
public static final int REQUEST_CODE_SAF_GUIDE = 98;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setButtonCtaVisible(false);
setButtonNextVisible(false);
setButtonBackVisible(false);
setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT);
String title = String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name));
addSlide(new SimpleSlide.Builder()
.title(title)
.description(Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1
? R.string.saf_guide_slide1_description_before_o : R.string.saf_guide_slide1_description)
.image(R.drawable.saf_guide_1)
.background(R.color.md_deep_purple_300)
.backgroundDark(R.color.md_deep_purple_400)
.layout(R.layout.fragment_simple_slide_large_image)
.build());
addSlide(new SimpleSlide.Builder()
.title(R.string.saf_guide_slide2_title)
.description(R.string.saf_guide_slide2_description)
.image(R.drawable.saf_guide_2)
.background(R.color.md_deep_purple_500)
.backgroundDark(R.color.md_deep_purple_600)
.layout(R.layout.fragment_simple_slide_large_image)
.build());
addSlide(new SimpleSlide.Builder()
.title(R.string.saf_guide_slide3_title)
.description(R.string.saf_guide_slide3_description)
.image(R.drawable.saf_guide_3)
.background(R.color.md_deep_purple_700)
.backgroundDark(R.color.md_deep_purple_800)
.layout(R.layout.fragment_simple_slide_large_image)
.build());
}
}

View file

@ -1,415 +0,0 @@
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.Build
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.view.animation.OvershootInterpolator
import androidx.appcompat.app.AlertDialog
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.drawable
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.SAFUtil
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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
import java.util.*
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: MaterialButton
private var savedSongPaths: List<String>? = null
private val currentSongPath: String? = null
private var savedTags: Map<FieldKey, String>? = null
private var savedArtworkInfo: ArtworkInfo? = null
protected val show: AlertDialog
get() =
MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_image)
.setItems(items.toTypedArray()) { _, position ->
when (position) {
0 -> startImagePicker()
1 -> searchImageOnWeb()
2 -> deleteImage()
}
}
.show()
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()
setStatusbarColorAuto()
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(code.name.monkey.retromusic.R.string.pick_from_local_storage),
getString(code.name.monkey.retromusic.R.string.web_search),
getString(code.name.monkey.retromusic.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(code.name.monkey.retromusic.R.string.pick_from_local_storage)
), REQUEST_CODE_SELECT_IMAGE
)
}
protected abstract fun loadCurrentImage()
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 {
when (item.itemId) {
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,
ATHUtil.resolveColor(this, R.attr.colorPrimary)
)
)
}
protected fun dataChanged() {
showFab()
}
private fun showFab() {
saveFab.animate().setDuration(500).setInterpolator(OvershootInterpolator()).scaleX(1f)
.scaleY(1f).start()
saveFab.isEnabled = true
}
private fun hideFab() {
saveFab.animate().setDuration(500).setInterpolator(OvershootInterpolator()).scaleX(0.0f)
.scaleY(0.0f).start()
saveFab.isEnabled = false
}
protected fun setImageBitmap(bitmap: Bitmap?, bgColor: Int) {
if (bitmap == null) {
editorImage.setImageResource(drawable.default_audio_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)
hideFab()
savedSongPaths = getSongPaths()
savedTags = fieldKeyValueMap
savedArtworkInfo = artworkInfo
if (!SAFUtil.isSAFRequired(savedSongPaths)) {
writeTags(savedSongPaths)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (SAFUtil.isSDCardAccessGranted(this)) {
writeTags(savedSongPaths)
} else {
startActivityForResult(
Intent(this, SAFGuideActivity::class.java),
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE
)
}
}
}
}
private fun writeTags(paths: List<String>?) {
WriteTagsAsyncTask(this).execute(
WriteTagsAsyncTask.LoadingInfo(
paths,
savedTags,
savedArtworkInfo
)
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
when (requestCode) {
REQUEST_CODE_SELECT_IMAGE -> if (resultCode == Activity.RESULT_OK) {
intent?.data?.let {
loadImageFromFile(it)
}
}
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE -> {
if (resultCode == Activity.RESULT_OK) {
SAFUtil.saveTreeUri(this, intent)
writeTags(savedSongPaths)
}
}
SAFUtil.REQUEST_SAF_PICK_FILE -> {
if (resultCode == Activity.RESULT_OK) {
writeTags(Collections.singletonList(currentSongPath + SAFUtil.SEPARATOR + intent!!.dataString))
}
}
}
}
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

@ -1,198 +0,0 @@
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.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.transition.Slide
import android.widget.Toast
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.loaders.AlbumLoader
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
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
private fun windowEnterTransition() {
val slide = Slide()
slide.excludeTarget(R.id.appBarLayout, true)
slide.excludeTarget(R.id.status_bar, true)
slide.excludeTarget(android.R.id.statusBarBackground, true)
slide.excludeTarget(android.R.id.navigationBarBackground, true)
window.enterTransition = slide
}
override fun loadImageFromFile(selectedFileUri: Uri?) {
Glide.with(this@AlbumTagEditorActivity).load(selectedFileUri).asBitmap()
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() {
override fun onResourceReady(
resource: BitmapPaletteWrapper?,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?
) {
getColor(resource?.palette, Color.TRANSPARENT)
albumArtBitmap = resource?.bitmap?.let { ImageUtil.resizeBitmap(it, 2048) }
setImageBitmap(
albumArtBitmap,
getColor(
resource?.palette,
ATHUtil.resolveColor(
this@AlbumTagEditorActivity,
R.attr.defaultFooterColor
)
)
)
deleteAlbumArt = false
dataChanged()
setResult(Activity.RESULT_OK)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
Toast.makeText(this@AlbumTagEditorActivity, e.toString(), Toast.LENGTH_LONG)
.show()
}
})
}
private var albumArtBitmap: Bitmap? = null
private var deleteAlbumArt: Boolean = false
private fun setupToolbar() {
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
}
override fun onCreate(savedInstanceState: Bundle?) {
setDrawUnderStatusBar()
super.onCreate(savedInstanceState)
window.sharedElementsUseOverlay = true
imageContainer?.transitionName = getString(R.string.transition_album_art)
windowEnterTransition()
setUpViews()
setupToolbar()
}
private fun setUpViews() {
fillViewsWithFileTags()
MaterialUtil.setTint(yearContainer, false)
MaterialUtil.setTint(genreContainer, false)
MaterialUtil.setTint(albumTitleContainer, false)
MaterialUtil.setTint(albumArtistContainer, false)
albumText.appHandleColor().addTextChangedListener(this)
albumArtistText.appHandleColor().addTextChangedListener(this)
genreTitle.appHandleColor().addTextChangedListener(this)
yearTitle.appHandleColor().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
}
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_audio_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 ArtworkInfo(id, albumArtBitmap!!)
)
}
override fun getSongPaths(): List<String> {
val songs = AlbumLoader.getAlbum(this, id).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

@ -1,112 +0,0 @@
package code.name.monkey.retromusic.activities.tageditor
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.appHandleColor
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
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setNoImageMode()
setUpViews()
toolbar.setBackgroundColor(ATHUtil.resolveColor(this, R.attr.colorSurface))
setSupportActionBar(toolbar)
}
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)
songText.appHandleColor().addTextChangedListener(this)
albumText.appHandleColor().addTextChangedListener(this)
albumArtistText.appHandleColor().addTextChangedListener(this)
artistText.appHandleColor().addTextChangedListener(this)
genreText.appHandleColor().addTextChangedListener(this)
yearText.appHandleColor().addTextChangedListener(this)
trackNumberText.appHandleColor().addTextChangedListener(this)
lyricsText.appHandleColor().addTextChangedListener(this)
songComposerText.appHandleColor().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 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).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

@ -1,193 +0,0 @@
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 android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
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.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
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;
import code.name.monkey.retromusic.util.SAFUtil;
public class WriteTagsAsyncTask extends
DialogAsyncTask<WriteTagsAsyncTask.LoadingInfo, Integer, String[]> {
private WeakReference<Activity> activity;
public WriteTagsAsyncTask(@NonNull Activity activity) {
super(activity);
this.activity = new WeakReference<>(activity);
}
@NonNull
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialAlertDialogBuilder(context)
.setTitle(R.string.saving_changes)
.setCancelable(false)
.setView(R.layout.loading)
.create();
}
@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 {
Uri safUri = null;
if (filePath.contains(SAFUtil.SEPARATOR)) {
String[] fragments = filePath.split(SAFUtil.SEPARATOR);
filePath = fragments[0];
safUri = Uri.parse(fragments[1]);
}
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;
}
}
Activity activity = this.activity.get();
SAFUtil.write(activity, audioFile, safUri);
} catch (@NonNull Exception 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());
}
}
Collection<String> paths = info.filePaths;
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
paths = new ArrayList<>(info.filePaths.size());
for (String path : info.filePaths) {
if (path.contains(SAFUtil.SEPARATOR)) {
path = path.split(SAFUtil.SEPARATOR)[0];
}
paths.add(path);
}
}
return paths.toArray(new String[paths.size()]);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onCancelled(String[] toBeScanned) {
super.onCancelled(toBeScanned);
scan(toBeScanned);
}
@Override
protected void onPostExecute(String[] toBeScanned) {
super.onPostExecute(toBeScanned);
scan(toBeScanned);
}
@Override
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
super.onProgressUpdate(dialog, values);
//((MaterialDialog) dialog).setMaxProgress(values[1]);
//((MaterialDialog) dialog).setProgress(values[0]);
}
private void scan(String[] toBeScanned) {
Activity activity = this.activity.get();
if (activity != null) {
MediaScannerConnection.scanFile(activity, toBeScanned, null,
new UpdateToastMediaScannerCompletionListener(activity, toBeScanned));
}
}
public static class LoadingInfo {
@Nullable
final Map<FieldKey, String> fieldKeyValueMap;
final Collection<String> filePaths;
@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;
}
}
}

View file

@ -1,137 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.adapter;
import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
import java.util.List;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.CategoryInfo;
import code.name.monkey.retromusic.util.SwipeAndDragHelper;
public class CategoryInfoAdapter extends RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>
implements SwipeAndDragHelper.ActionCompletionContract {
private List<CategoryInfo> categoryInfos;
private ItemTouchHelper touchHelper;
public CategoryInfoAdapter() {
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(this);
touchHelper = new ItemTouchHelper(swipeAndDragHelper);
}
public void attachToRecyclerView(RecyclerView recyclerView) {
touchHelper.attachToRecyclerView(recyclerView);
}
@NonNull
public List<CategoryInfo> getCategoryInfos() {
return categoryInfos;
}
public void setCategoryInfos(@NonNull List<CategoryInfo> categoryInfos) {
this.categoryInfos = categoryInfos;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return categoryInfos.size();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindViewHolder(@NonNull CategoryInfoAdapter.ViewHolder holder, int position) {
CategoryInfo categoryInfo = categoryInfos.get(position);
holder.checkBox.setChecked(categoryInfo.visible);
holder.title.setText(holder.title.getResources().getString(categoryInfo.category.stringRes));
holder.itemView.setOnClickListener(v -> {
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
categoryInfo.visible = !categoryInfo.visible;
holder.checkBox.setChecked(categoryInfo.visible);
} else {
Toast.makeText(holder.itemView.getContext(), R.string.you_have_to_select_at_least_one_category,
Toast.LENGTH_SHORT).show();
}
});
holder.dragView.setOnTouchListener((view, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder);
}
return false;
}
);
}
@Override
@NonNull
public CategoryInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.preference_dialog_library_categories_listitem, parent, false);
return new ViewHolder(view);
}
@Override
public void onViewMoved(int oldPosition, int newPosition) {
CategoryInfo categoryInfo = categoryInfos.get(oldPosition);
categoryInfos.remove(oldPosition);
categoryInfos.add(newPosition, categoryInfo);
notifyItemMoved(oldPosition, newPosition);
}
private boolean isLastCheckedCategory(CategoryInfo categoryInfo) {
if (categoryInfo.visible) {
for (CategoryInfo c : categoryInfos) {
if (c != categoryInfo && c.visible) {
return false;
}
}
}
return true;
}
static class ViewHolder extends RecyclerView.ViewHolder {
private MaterialCheckBox checkBox;
private View dragView;
private TextView title;
ViewHolder(View view) {
super(view);
checkBox = view.findViewById(R.id.checkbox);
checkBox.setButtonTintList(
ColorStateList.valueOf(ThemeStore.Companion.accentColor(checkBox.getContext())));
title = view.findViewById(R.id.title);
dragView = view.findViewById(R.id.drag_view);
}
}
}

View file

@ -1,78 +0,0 @@
package code.name.monkey.retromusic.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Contributor
import code.name.monkey.retromusic.util.RetroUtil.openUrl
import code.name.monkey.retromusic.views.RetroShapeableImageView
import com.bumptech.glide.Glide
class ContributorAdapter(
private var contributors: List<Contributor>
) : RecyclerView.Adapter<ContributorAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == HEADER) {
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor_header,
parent,
false
)
)
} else ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor,
parent,
false
)
)
}
companion object {
const val HEADER: Int = 0
const val ITEM: Int = 1
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) {
HEADER
} else {
ITEM
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contributor = contributors[position]
holder.bindData(contributor)
holder.itemView.setOnClickListener {
openUrl(it?.context as Activity, contributors[position].link)
}
}
override fun getItemCount(): Int {
return contributors.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val text: TextView = itemView.findViewById(R.id.text)
val image: RetroShapeableImageView = itemView.findViewById(R.id.icon)
internal fun bindData(contributor: Contributor) {
title.text = contributor.name
text.text = contributor.summary
Glide.with(image.context)
.load(contributor.profileImage)
.error(R.drawable.ic_account_white_24dp)
.placeholder(R.drawable.ic_account_white_24dp)
.dontAnimate()
.into(image)
}
}
}

View file

@ -1,56 +0,0 @@
package code.name.monkey.retromusic.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.util.NavigationUtil
import java.util.*
/**
* @author Hemanth S (h4h13).
*/
class GenreAdapter(
private val activity: Activity,
var dataSet: List<Genre>,
private val mItemLayoutRes: Int
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val genre = dataSet[position]
holder.title?.text = genre.name
holder.text?.text = String.format(
Locale.getDefault(),
"%d %s",
genre.songCount,
if (genre.songCount > 1) activity.getString(R.string.songs) else activity.getString(R.string.song)
)
}
override fun getItemCount(): Int {
return dataSet.size
}
fun swapDataSet(list: List<Genre>) {
dataSet = list
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
override fun onClick(v: View?) {
super.onClick(v)
val genre = dataSet[layoutPosition]
NavigationUtil.goToGenre(activity, genre)
}
}
}

View file

@ -1,182 +0,0 @@
package code.name.monkey.retromusic.adapter
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IntDef
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.AlbumFullWidthAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Home
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.util.PreferenceUtil
class HomeAdapter(
private val activity: AppCompatActivity,
private val displayMetrics: DisplayMetrics
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var list = listOf<Home>()
override fun getItemViewType(position: Int): Int {
return list[position].homeSection
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = LayoutInflater.from(activity)
.inflate(R.layout.section_recycler_view, parent, false)
return when (viewType) {
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
PLAYLISTS -> PlaylistViewHolder(layout)
else -> {
AlbumViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.metal_section_recycler_view,
parent,
false
)
)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
RECENT_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.recent_albums)
}
TOP_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(list[position].arrayList.toAlbums(), R.string.top_albums)
}
RECENT_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(list[position].arrayList.toArtists(), R.string.recent_artists)
}
TOP_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(list[position].arrayList.toArtists(), R.string.top_artists)
}
PLAYLISTS -> {
val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(list[position].arrayList.toPlaylist(), R.string.favorites)
}
}
}
override fun getItemCount(): Int {
return list.size
}
fun swapData(sections: List<Home>) {
list = sections
notifyDataSetChanged()
}
companion object {
@IntDef(RECENT_ALBUMS, TOP_ALBUMS, RECENT_ARTISTS, TOP_ARTISTS, PLAYLISTS)
@Retention(AnnotationRetention.SOURCE)
annotation class HomeSection
const val RECENT_ALBUMS = 3
const val TOP_ALBUMS = 1
const val RECENT_ARTISTS = 2
const val TOP_ARTISTS = 0
const val PLAYLISTS = 4
}
private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(list: ArrayList<Album>, titleRes: Int) {
if (list.isNotEmpty()) {
recyclerView.apply {
show()
adapter = AlbumFullWidthAdapter(activity, list, displayMetrics)
}
title.text = activity.getString(titleRes)
}
}
}
inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(list: ArrayList<Artist>, titleRes: Int) {
if (list.isNotEmpty()) {
val manager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
val artistAdapter = ArtistAdapter(
activity,
list,
PreferenceUtil.homeGridStyle,
null
)
recyclerView.apply {
show()
layoutManager = manager
adapter = artistAdapter
}
title.text = activity.getString(titleRes)
}
}
}
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(arrayList: ArrayList<Playlist>, titleRes: Int) {
if (arrayList.isNotEmpty()) {
val songs = PlaylistSongsLoader.getPlaylistSongList(activity, arrayList[0])
if (songs.isNotEmpty()) {
recyclerView.apply {
show()
val songAdapter =
SongAdapter(activity, songs, R.layout.item_album_card, null)
layoutManager =
GridLayoutManager(activity, 1, GridLayoutManager.HORIZONTAL, false)
adapter = songAdapter
}
title.text = activity.getString(titleRes)
}
}
}
}
open inner class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
val title: AppCompatTextView = itemView.findViewById(R.id.title)
}
}
private fun <E> ArrayList<E>.toAlbums(): ArrayList<Album> {
val arrayList = ArrayList<Album>()
for (x in this) {
arrayList.add(x as Album)
}
return arrayList
}
private fun <E> ArrayList<E>.toArtists(): ArrayList<Artist> {
val arrayList = ArrayList<Artist>()
for (x in this) {
arrayList.add(x as Artist)
}
return arrayList
}
private fun <E> ArrayList<E>.toPlaylist(): ArrayList<Playlist> {
val arrayList = ArrayList<Playlist>()
for (x in this) {
arrayList.add(x as Playlist)
}
return arrayList
}

View file

@ -1,172 +0,0 @@
package code.name.monkey.retromusic.adapter
import android.app.ActivityOptions
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import com.bumptech.glide.Glide
import android.util.Pair as UtilPair
class SearchAdapter(
private val activity: AppCompatActivity,
private var dataSet: List<Any>?
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
fun swapDataSet(dataSet: MutableList<Any>) {
this.dataSet = dataSet
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
if (dataSet!![position] is Album) return ALBUM
if (dataSet!![position] is Artist) return ARTIST
if (dataSet!![position] is Genre) return GENRE
if (dataSet!![position] is Playlist) return PLAYLIST
return if (dataSet!![position] is Song) SONG else HEADER
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == HEADER) ViewHolder(
LayoutInflater.from(activity).inflate(
R.layout.sub_header,
parent,
false
), viewType
)
else
ViewHolder(
LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false),
viewType
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (getItemViewType(position)) {
ALBUM -> {
val album = dataSet?.get(position) as Album
holder.title?.text = album.title
holder.text?.text = album.artistName
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity).build().into(holder.image)
}
ARTIST -> {
val artist = dataSet?.get(position) as Artist
holder.title?.text = artist.name
holder.text?.text = MusicUtil.getArtistInfoString(activity, artist)
ArtistGlideRequest.Builder.from(Glide.with(activity), artist).build()
.into(holder.image)
}
SONG -> {
val song = dataSet?.get(position) as Song
holder.title?.text = song.title
holder.text?.text = song.albumName
}
GENRE -> {
val genre = dataSet?.get(position) as Genre
holder.title?.text = genre.name
}
PLAYLIST -> {
val playlist = dataSet?.get(position) as Playlist
holder.title?.text = playlist.name
holder.text?.text = MusicUtil.getPlaylistInfoString(activity, getSongs(playlist))
}
else -> {
holder.title?.text = dataSet?.get(position).toString()
holder.title?.setTextColor(ThemeStore.accentColor(activity))
}
}
}
private fun getSongs(playlist: Playlist): java.util.ArrayList<Song> {
val songs = java.util.ArrayList<Song>()
if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs(activity))
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}
return songs
}
override fun getItemCount(): Int {
return dataSet!!.size
}
inner class ViewHolder(itemView: View, itemViewType: Int) : MediaEntryViewHolder(itemView) {
init {
itemView.setOnLongClickListener(null)
if (itemViewType == SONG) {
menu?.visibility = View.VISIBLE
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
override val song: Song
get() = dataSet!![layoutPosition] as Song
})
} else {
menu?.visibility = View.GONE
}
when (itemViewType) {
ALBUM -> setImageTransitionName(activity.getString(R.string.transition_album_art))
ARTIST -> setImageTransitionName(activity.getString(R.string.transition_artist_image))
else -> {
val container = itemView.findViewById<View>(R.id.imageContainer)
container?.visibility = View.GONE
}
}
}
override fun onClick(v: View?) {
val item = dataSet!![layoutPosition]
when (itemViewType) {
ALBUM -> {
val options = ActivityOptions.makeSceneTransitionAnimation(
activity,
UtilPair.create(image, activity.getString(R.string.transition_album_art))
)
NavigationUtil.goToAlbumOptions(activity, (item as Album).id, options)
}
ARTIST -> {
val options = ActivityOptions.makeSceneTransitionAnimation(
activity,
UtilPair.create(image, activity.getString(R.string.transition_artist_image))
)
NavigationUtil.goToArtistOptions(activity, (item as Artist).id, options)
}
GENRE -> {
NavigationUtil.goToGenre(activity, item as Genre)
}
PLAYLIST -> {
NavigationUtil.goToPlaylistNew(activity, item as Playlist)
}
SONG -> {
val playList = ArrayList<Song>()
playList.add(item as Song)
MusicPlayerRemote.openQueue(playList, 0, true)
}
}
}
}
companion object {
private const val HEADER = 0
private const val ALBUM = 1
private const val ARTIST = 2
private const val SONG = 3
private const val GENRE = 4
private const val PLAYLIST = 5
}
}

View file

@ -1,208 +0,0 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package code.name.monkey.retromusic.adapter
import android.graphics.PorterDuff
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.MediaStoreSignature
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.io.File
import java.text.DecimalFormat
import java.util.*
import kotlin.math.log10
import kotlin.math.pow
class SongFileAdapter(
private val activity: AppCompatActivity,
private var dataSet: List<File>,
private val itemLayoutRes: Int,
private val callbacks: Callbacks?,
cabHolder: CabHolder?
) : AbsMultiSelectAdapter<SongFileAdapter.ViewHolder, File>(
activity, cabHolder, R.menu.menu_media_selection
), PopupTextProvider {
init {
this.setHasStableIds(true)
}
override fun getItemViewType(position: Int): Int {
return if (dataSet[position].isDirectory) FOLDER else FILE
}
override fun getItemId(position: Int): Long {
return dataSet[position].hashCode().toLong()
}
fun swapDataSet(songFiles: List<File>) {
this.dataSet = songFiles
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, index: Int) {
val file = dataSet[index]
holder.itemView.isActivated = isChecked(file)
holder.title?.text = getFileTitle(file)
if (holder.text != null) {
if (holder.itemViewType == FILE) {
holder.text?.text = getFileText(file)
} else {
holder.text?.visibility = View.GONE
}
}
if (holder.image != null) {
loadFileImage(file, holder)
}
}
private fun getFileTitle(file: File): String {
return file.name
}
private fun getFileText(file: File): String? {
return if (file.isDirectory) null else readableFileSize(file.length())
}
private fun loadFileImage(file: File, holder: ViewHolder) {
val iconColor = ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
if (file.isDirectory) {
holder.image?.let {
it.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN)
it.setImageResource(R.drawable.ic_folder_white_24dp)
}
holder.imageTextContainer?.setCardBackgroundColor(
ATHUtil.resolveColor(
activity,
R.attr.colorSurface
)
)
} else {
val error = RetroUtil.getTintedVectorDrawable(
activity, R.drawable.ic_file_music_white_24dp, iconColor
)
Glide.with(activity)
.load(AudioFileCover(file.path))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(error)
.placeholder(error)
.animate(android.R.anim.fade_in)
.signature(MediaStoreSignature("", file.lastModified(), 0))
.into(holder.image)
}
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun getIdentifier(position: Int): File? {
return dataSet[position]
}
override fun getName(`object`: File): String {
return getFileTitle(`object`)
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<File>) {
if (callbacks == null) return
callbacks.onMultipleItemAction(menuItem, selection)
}
override fun getPopupText(position: Int): String {
return getSectionName(position)
}
private fun getSectionName(position: Int): String {
return MusicUtil.getSectionName(dataSet[position].name)
}
interface Callbacks {
fun onFileSelected(file: File)
fun onFileMenuClicked(file: File, view: View)
fun onMultipleItemAction(item: MenuItem, files: ArrayList<File>)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init {
if (menu != null && callbacks != null) {
menu?.setOnClickListener { v ->
val position = layoutPosition
if (isPositionInRange(position)) {
callbacks.onFileMenuClicked(dataSet[position], v)
}
}
}
if (imageTextContainer != null) {
imageTextContainer?.cardElevation = 0f
}
}
override fun onClick(v: View?) {
val position = layoutPosition
if (isPositionInRange(position)) {
if (isInQuickSelectMode) {
toggleChecked(position)
} else {
callbacks?.onFileSelected(dataSet[position])
}
}
}
override fun onLongClick(v: View?): Boolean {
val position = layoutPosition
return isPositionInRange(position) && toggleChecked(position)
}
private fun isPositionInRange(position: Int): Boolean {
return position >= 0 && position < dataSet.size
}
}
companion object {
private const val FILE = 0
private const val FOLDER = 1
fun readableFileSize(size: Long): String {
if (size <= 0) return "$size B"
val units = arrayOf("B", "KB", "MB", "GB", "TB")
val digitGroups = (log10(size.toDouble()) / log10(1024.0)).toInt()
return DecimalFormat("#,##0.##").format(size / 1024.0.pow(digitGroups.toDouble())) + " " + units[digitGroups]
}
}
}

View file

@ -1,52 +0,0 @@
package code.name.monkey.retromusic.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.model.Contributor
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.views.RetroShapeableImageView
class TranslatorsAdapter(
private var contributors: List<Contributor>
) : RecyclerView.Adapter<TranslatorsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_contributor,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contributor = contributors[position]
holder.bindData(contributor)
holder.itemView.setOnClickListener {
RetroUtil.openUrl(it?.context as Activity, contributors[position].link)
}
}
override fun getItemCount(): Int {
return contributors.size
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val text: TextView = itemView.findViewById(R.id.text)
val image: RetroShapeableImageView = itemView.findViewById(R.id.icon)
internal fun bindData(contributor: Contributor) {
title.text = contributor.name
text.text = contributor.summary
image.hide()
}
}
}

View file

@ -1,202 +0,0 @@
package code.name.monkey.retromusic.adapter.album
import android.app.ActivityOptions
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Song
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.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider
open class AlbumAdapter(
protected val activity: AppCompatActivity,
var dataSet: List<Album>,
protected var itemLayoutRes: Int,
cabHolder: CabHolder?
) : AbsMultiSelectAdapter<AlbumAdapter.ViewHolder, Album>(
activity,
cabHolder,
R.menu.menu_media_selection
), PopupTextProvider {
init {
this.setHasStableIds(true)
}
fun swapDataSet(dataSet: List<Album>) {
this.dataSet = dataSet
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
try {
LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
} catch (e: Resources.NotFoundException) {
LayoutInflater.from(activity).inflate(R.layout.item_grid, parent, false)
}
return createViewHolder(view, viewType)
}
protected open fun createViewHolder(view: View, viewType: Int): ViewHolder {
return ViewHolder(view)
}
private fun getAlbumTitle(album: Album): String? {
return album.title
}
protected open fun getAlbumText(album: Album): String? {
return album.artistName
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val album = dataSet[position]
val isChecked = isChecked(album)
holder.itemView.isActivated = isChecked
holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album)
holder.playSongs?.setOnClickListener {
album.songs?.let { songs ->
MusicPlayerRemote.openQueue(
songs,
0,
true
)
}
}
loadAlbumCover(album, holder)
}
protected open fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
if (holder.paletteColorContainer != null) {
holder.title?.setTextColor(color.primaryTextColor)
holder.text?.setTextColor(color.secondaryTextColor)
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
}
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
}
protected open fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) {
return
}
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity)
.generatePalette(activity)
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onLoadCleared(placeholder: Drawable?) {
super.onLoadCleared(placeholder)
//setColors(defaultFooterColor, holder)
}
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
}
})
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun getItemId(position: Int): Long {
return dataSet[position].id.toLong()
}
override fun getIdentifier(position: Int): Album? {
return dataSet[position]
}
override fun getName(album: Album): String {
return album.title!!
}
override fun onMultipleItemAction(
menuItem: MenuItem, selection: ArrayList<Album>
) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
}
private fun getSongList(albums: List<Album>): ArrayList<Song> {
val songs = ArrayList<Song>()
for (album in albums) {
songs.addAll(album.songs!!)
}
return songs
}
override fun getPopupText(position: Int): String {
return getSectionName(position)
}
private fun getSectionName(position: Int): String {
var sectionName: String? = null
when (PreferenceUtil.albumSortOrder) {
SortOrder.AlbumSortOrder.ALBUM_A_Z, SortOrder.AlbumSortOrder.ALBUM_Z_A -> sectionName =
dataSet[position].title
SortOrder.AlbumSortOrder.ALBUM_ARTIST -> sectionName = dataSet[position].artistName
SortOrder.AlbumSortOrder.ALBUM_YEAR -> return MusicUtil.getYearString(
dataSet[position].year
)
}
return MusicUtil.getSectionName(sectionName)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init {
setImageTransitionName(activity.getString(R.string.transition_album_art))
menu?.visibility = View.GONE
}
override fun onClick(v: View?) {
super.onClick(v)
if (isInQuickSelectMode) {
toggleChecked(layoutPosition)
} else {
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
activity,
imageContainerCard ?: image,
activity.getString(R.string.transition_album_art)
)
NavigationUtil.goToAlbumOptions(
activity,
dataSet[layoutPosition].id,
activityOptions
)
}
}
override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition)
return super.onLongClick(v)
}
}
companion object {
val TAG: String = AlbumAdapter::class.java.simpleName
}
}

View file

@ -1,181 +0,0 @@
package code.name.monkey.retromusic.adapter.album
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.misc.CustomFragmentStatePagerAdapter
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
class AlbumCoverPagerAdapter(
fragmentManager: FragmentManager,
private val dataSet: List<Song>
) : CustomFragmentStatePagerAdapter(fragmentManager) {
private var currentColorReceiver: AlbumCoverFragment.ColorReceiver? = null
private var currentColorReceiverPosition = -1
override fun getItem(position: Int): Fragment {
return AlbumCoverFragment.newInstance(dataSet[position])
}
override fun getCount(): Int {
return dataSet.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val o = super.instantiateItem(container, position)
if (currentColorReceiver != null && currentColorReceiverPosition == position) {
receiveColor(currentColorReceiver!!, currentColorReceiverPosition)
}
return o
}
/**
* Only the latest passed [AlbumCoverFragment.ColorReceiver] is guaranteed to receive a
* response
*/
fun receiveColor(colorReceiver: AlbumCoverFragment.ColorReceiver, position: Int) {
if (getFragment(position) is AlbumCoverFragment) {
val fragment = getFragment(position) as AlbumCoverFragment
currentColorReceiver = null
currentColorReceiverPosition = -1
fragment.receiveColor(colorReceiver, position)
} else {
currentColorReceiver = colorReceiver
currentColorReceiverPosition = position
}
}
class AlbumCoverFragment : Fragment() {
lateinit var albumCover: ImageView
private var isColorReady: Boolean = false
private lateinit var color: MediaNotificationProcessor
private lateinit var song: Song
private var colorReceiver: ColorReceiver? = null
private var request: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments != null) {
song = requireArguments().getParcelable(SONG_ARG)!!
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
albumCover = view.findViewById(R.id.player_image)
albumCover.setOnClickListener {
NavigationUtil.goToLyrics(requireActivity())
}
return view
}
private fun getLayoutWithPlayerTheme(): Int {
return when (PreferenceUtil.nowPlayingScreen) {
Card,
Fit,
Tiny,
Classic,
Peak,
Gradient,
Full -> R.layout.fragment_album_full_cover
else -> {
if (PreferenceUtil.isCarouselEffect) {
R.layout.fragment_album_carousel_cover
} else {
when (PreferenceUtil.albumCoverStyle) {
AlbumCoverStyle.Normal -> R.layout.fragment_album_cover
AlbumCoverStyle.Flat -> R.layout.fragment_album_flat_cover
AlbumCoverStyle.Circle -> R.layout.fragment_album_circle_cover
AlbumCoverStyle.Card -> R.layout.fragment_album_card_cover
AlbumCoverStyle.Material -> R.layout.fragment_album_material_cover
AlbumCoverStyle.Full -> R.layout.fragment_album_full_cover
AlbumCoverStyle.FullCard -> R.layout.fragment_album_full_card_cover
}
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadAlbumCover()
}
override fun onDestroyView() {
super.onDestroyView()
colorReceiver = null
}
private fun loadAlbumCover() {
SongGlideRequest.Builder.from(Glide.with(requireContext()), song)
.checkIgnoreMediaStore(requireContext())
.generatePalette(requireContext()).build()
.into(object : RetroMusicColoredTarget(albumCover) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColor(colors)
}
})
}
private fun setColor(color: MediaNotificationProcessor) {
this.color = color
isColorReady = true
if (colorReceiver != null) {
colorReceiver!!.onColorReady(color, request)
colorReceiver = null
}
}
internal fun receiveColor(colorReceiver: ColorReceiver, request: Int) {
if (isColorReady) {
colorReceiver.onColorReady(color, request)
} else {
this.colorReceiver = colorReceiver
this.request = request
}
}
interface ColorReceiver {
fun onColorReady(color: MediaNotificationProcessor, request: Int)
}
companion object {
private const val SONG_ARG = "song"
fun newInstance(song: Song): AlbumCoverFragment {
val frag = AlbumCoverFragment()
val args = Bundle()
args.putParcelable(SONG_ARG, song)
frag.arguments = args
return frag
}
}
}
companion object {
val TAG: String = AlbumCoverPagerAdapter::class.java.simpleName
}
}

View file

@ -1,108 +0,0 @@
/*
* Copyright (C) 2017. Alexander Bilchuk <a.bilchuk@sandrlab.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package code.name.monkey.retromusic.adapter.album
import android.app.Activity
import android.app.ActivityOptions
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import code.name.monkey.retromusic.views.MetalRecyclerViewPager
import com.bumptech.glide.Glide
class AlbumFullWidthAdapter(
private val activity: Activity,
private val dataSet: List<Album>,
metrics: DisplayMetrics
) : MetalRecyclerViewPager.MetalAdapter<AlbumFullWidthAdapter.FullMetalViewHolder>(metrics) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FullMetalViewHolder {
return FullMetalViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.pager_item,
parent,
false
)
)
}
override fun onBindViewHolder(holder: FullMetalViewHolder, position: Int) {
// don't forget about calling supper.onBindViewHolder!
super.onBindViewHolder(holder, position)
val album = dataSet[position]
holder.title?.text = getAlbumTitle(album)
holder.text?.text = getAlbumText(album)
holder.playSongs?.setOnClickListener {
album.songs?.let { songs ->
MusicPlayerRemote.openQueue(
songs,
0,
true
)
}
}
loadAlbumCover(album, holder)
}
private fun getAlbumTitle(album: Album): String? {
return album.title
}
private fun getAlbumText(album: Album): String? {
return album.artistName
}
private fun loadAlbumCover(album: Album, holder: FullMetalViewHolder) {
if (holder.image == null) {
return
}
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity)
.generatePalette(activity)
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
}
override fun getItemCount(): Int {
return dataSet.size
}
inner class FullMetalViewHolder(itemView: View) :
MetalRecyclerViewPager.MetalViewHolder(itemView) {
override fun onClick(v: View?) {
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
activity,
imageContainerCard ?: image,
activity.getString(R.string.transition_album_art)
)
NavigationUtil.goToAlbumOptions(activity, dataSet[layoutPosition].id, activityOptions)
}
}
}

View file

@ -1,69 +0,0 @@
package code.name.monkey.retromusic.adapter.album
import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.HorizontalAdapterHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
class HorizontalAlbumAdapter(
activity: AppCompatActivity,
dataSet: List<Album>,
cabHolder: CabHolder?
) : AlbumAdapter(
activity, dataSet, HorizontalAdapterHelper.LAYOUT_RES, cabHolder
) {
override fun createViewHolder(view: View, viewType: Int): ViewHolder {
val params = view.layoutParams as ViewGroup.MarginLayoutParams
HorizontalAdapterHelper.applyMarginToLayoutParams(activity, params, viewType)
return ViewHolder(view)
}
override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary))
holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary))
}
override fun loadAlbumCover(album: Album, holder: ViewHolder) {
if (holder.image == null) return
AlbumGlideRequest.Builder.from(Glide.with(activity), album.safeGetFirstSong())
.checkIgnoreMediaStore(activity)
.generatePalette(activity)
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onLoadCleared(placeholder: Drawable?) {
super.onLoadCleared(placeholder)
//setColors(albumArtistFooterColor, holder)
}
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
}
})
}
override fun getAlbumText(album: Album): String? {
return MusicUtil.getYearString(album.year)
}
override fun getItemViewType(position: Int): Int {
return HorizontalAdapterHelper.getItemViewtype(position, itemCount)
}
override fun getItemCount(): Int {
return dataSet.size
}
companion object {
val TAG: String = AlbumAdapter::class.java.simpleName
}
}

View file

@ -1,158 +0,0 @@
package code.name.monkey.retromusic.adapter.artist
import android.app.ActivityOptions
import android.content.res.ColorStateList
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.util.*
class ArtistAdapter(
val activity: AppCompatActivity,
var dataSet: List<Artist>,
var itemLayoutRes: Int,
cabHolder: CabHolder?
) : AbsMultiSelectAdapter<ArtistAdapter.ViewHolder, Artist>(
activity, cabHolder, R.menu.menu_media_selection
), PopupTextProvider {
init {
this.setHasStableIds(true)
}
fun swapDataSet(dataSet: List<Artist>) {
this.dataSet = dataSet
notifyDataSetChanged()
}
override fun getItemId(position: Int): Long {
return dataSet[position].id.toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
try {
LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
} catch (e: Resources.NotFoundException) {
LayoutInflater.from(activity).inflate(R.layout.item_grid_circle, parent, false)
}
return createViewHolder(view)
}
private fun createViewHolder(view: View): ViewHolder {
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val artist = dataSet[position]
val isChecked = isChecked(artist)
holder.itemView.isActivated = isChecked
holder.title?.text = artist.name
holder.text?.hide()
loadArtistImage(artist, holder)
}
fun setColors(processor: MediaNotificationProcessor, holder: ViewHolder) {
holder.mask?.backgroundTintList = ColorStateList.valueOf(processor.primaryTextColor)
if (holder.paletteColorContainer != null) {
holder.paletteColorContainer?.setBackgroundColor(processor.backgroundColor)
holder.title?.setTextColor(processor.primaryTextColor)
}
holder.imageContainerCard?.setCardBackgroundColor(processor.backgroundColor)
}
private fun loadArtistImage(artist: Artist, holder: ViewHolder) {
if (holder.image == null) {
return
}
ArtistGlideRequest.Builder.from(Glide.with(activity), artist)
.generatePalette(activity)
.build()
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
}
})
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun getIdentifier(position: Int): Artist? {
return dataSet[position]
}
override fun getName(artist: Artist): String {
return artist.name
}
override fun onMultipleItemAction(
menuItem: MenuItem, selection: ArrayList<Artist>
) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
}
private fun getSongList(artists: List<Artist>): ArrayList<Song> {
val songs = ArrayList<Song>()
for (artist in artists) {
songs.addAll(artist.songs) // maybe async in future?
}
return songs
}
override fun getPopupText(position: Int): String {
return getSectionName(position)
}
private fun getSectionName(position: Int): String {
return MusicUtil.getSectionName(dataSet[position].name)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init {
setImageTransitionName(activity.getString(R.string.transition_artist_image))
menu?.visibility = View.GONE
}
override fun onClick(v: View?) {
super.onClick(v)
if (isInQuickSelectMode) {
toggleChecked(layoutPosition)
} else {
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
activity,
imageContainerCard ?: image,
activity.getString(R.string.transition_artist_image)
)
NavigationUtil.goToArtistOptions(
activity, dataSet[layoutPosition].id, activityOptions
)
}
}
override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition)
return super.onLongClick(v)
}
}
}

View file

@ -1,133 +0,0 @@
package code.name.monkey.retromusic.adapter.base;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.MenuRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.CabHolder;
public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I> extends RecyclerView.Adapter<V>
implements MaterialCab.Callback {
@Nullable
private final CabHolder cabHolder;
private final Context context;
private MaterialCab cab;
private ArrayList<I> checked;
private int menuRes;
public AbsMultiSelectAdapter(@NonNull Context context, @Nullable CabHolder cabHolder, @MenuRes int menuRes) {
this.cabHolder = cabHolder;
checked = new ArrayList<>();
this.menuRes = menuRes;
this.context = context;
}
@Override
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
return true;
}
@Override
public boolean onCabFinished(MaterialCab materialCab) {
clearChecked();
return true;
}
@Override
public boolean onCabItemClicked(MenuItem menuItem) {
if (menuItem.getItemId() == R.id.action_multi_select_adapter_check_all) {
checkAll();
} else {
onMultipleItemAction(menuItem, new ArrayList<>(checked));
cab.finish();
clearChecked();
}
return true;
}
protected void checkAll() {
if (cabHolder != null) {
checked.clear();
for (int i = 0; i < getItemCount(); i++) {
I identifier = getIdentifier(i);
if (identifier != null) {
checked.add(identifier);
}
}
notifyDataSetChanged();
updateCab();
}
}
@Nullable
protected abstract I getIdentifier(int position);
protected String getName(I object) {
return object.toString();
}
protected boolean isChecked(I identifier) {
return checked.contains(identifier);
}
protected boolean isInQuickSelectMode() {
return cab != null && cab.isActive();
}
protected abstract void onMultipleItemAction(MenuItem menuItem, ArrayList<I> selection);
protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
this.menuRes = menuRes;
}
protected boolean toggleChecked(final int position) {
if (cabHolder != null) {
I identifier = getIdentifier(position);
if (identifier == null) {
return false;
}
if (!checked.remove(identifier)) {
checked.add(identifier);
}
notifyItemChanged(position);
updateCab();
return true;
}
return false;
}
private void clearChecked() {
checked.clear();
notifyDataSetChanged();
}
private void updateCab() {
if (cabHolder != null) {
if (cab == null || !cab.isActive()) {
cab = cabHolder.openCab(menuRes, this);
}
final int size = checked.size();
if (size <= 0) {
cab.finish();
} else if (size == 1) {
cab.setTitle(getName(checked.get(0)));
} else {
cab.setTitle(context.getString(R.string.x_selected, size));
}
}
}
}

View file

@ -1,139 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.adapter.base;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableSwipeableItemViewHolder;
import code.name.monkey.retromusic.R;
public class MediaEntryViewHolder extends AbstractDraggableSwipeableItemViewHolder
implements View.OnLongClickListener, View.OnClickListener {
@Nullable
public View dragView;
@Nullable
public View dummyContainer;
@Nullable
public ImageView image;
@Nullable
public ImageView artistImage;
@Nullable
public ImageView playerImage;
@Nullable
public ViewGroup imageContainer;
@Nullable
public MaterialCardView imageContainerCard;
@Nullable
public TextView imageText;
@Nullable
public MaterialCardView imageTextContainer;
@Nullable
public View mask;
@Nullable
public View menu;
@Nullable
public View paletteColorContainer;
@Nullable
public ImageButton playSongs;
@Nullable
public RecyclerView recyclerView;
@Nullable
public TextView text;
@Nullable
public TextView time;
@Nullable
public TextView title;
public MediaEntryViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.title);
text = itemView.findViewById(R.id.text);
image = itemView.findViewById(R.id.image);
artistImage = itemView.findViewById(R.id.artistImage);
playerImage = itemView.findViewById(R.id.player_image);
time = itemView.findViewById(R.id.time);
imageText = itemView.findViewById(R.id.imageText);
imageContainer = itemView.findViewById(R.id.imageContainer);
imageTextContainer = itemView.findViewById(R.id.imageTextContainer);
imageContainerCard = itemView.findViewById(R.id.imageContainerCard);
menu = itemView.findViewById(R.id.menu);
dragView = itemView.findViewById(R.id.drag_view);
paletteColorContainer = itemView.findViewById(R.id.paletteColorContainer);
recyclerView = itemView.findViewById(R.id.recycler_view);
mask = itemView.findViewById(R.id.mask);
playSongs = itemView.findViewById(R.id.playSongs);
dummyContainer = itemView.findViewById(R.id.dummy_view);
if (imageContainerCard != null) {
imageContainerCard.setCardBackgroundColor(Color.TRANSPARENT);
}
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
@Override
public View getSwipeableContainerView() {
return null;
}
@Override
public void onClick(View v) {
}
@Override
public boolean onLongClick(View v) {
return false;
}
public void setImageTransitionName(@NonNull String transitionName) {
if (imageContainerCard != null) {
imageContainerCard.setTransitionName(transitionName);
}
if (image != null) {
image.setTransitionName(transitionName);
}
}
}

View file

@ -1,225 +0,0 @@
package code.name.monkey.retromusic.adapter.playlist
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.AsyncTask
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
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.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import java.util.*
class PlaylistAdapter(
private val activity: AppCompatActivity,
var dataSet: List<Playlist>,
private var itemLayoutRes: Int,
cabHolder: CabHolder?
) : AbsMultiSelectAdapter<PlaylistAdapter.ViewHolder, Playlist>(
activity,
cabHolder,
R.menu.menu_playlists_selection
) {
init {
setHasStableIds(true)
}
fun swapDataSet(dataSet: List<Playlist>) {
this.dataSet = dataSet
notifyDataSetChanged()
}
override fun getItemId(position: Int): Long {
return dataSet[position].id.toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
return createViewHolder(view)
}
fun createViewHolder(view: View): ViewHolder {
return ViewHolder(view)
}
private fun getPlaylistTitle(playlist: Playlist): String {
return if (TextUtils.isEmpty(playlist.name)) "-" else playlist.name
}
private fun getPlaylistText(playlist: Playlist): String {
return MusicUtil.getPlaylistInfoString(activity, getSongs(playlist))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist = dataSet[position]
holder.itemView.isActivated = isChecked(playlist)
holder.title?.text = getPlaylistTitle(playlist)
holder.text?.text = getPlaylistText(playlist)
holder.image?.setImageDrawable(getIconRes(playlist))
val isChecked = isChecked(playlist)
if (isChecked) {
holder.menu?.hide()
} else {
holder.menu?.show()
}
//PlaylistBitmapLoader(this, holder, playlist).execute()
}
private fun getIconRes(playlist: Playlist): Drawable {
return if (MusicUtil.isFavoritePlaylist(activity, playlist))
TintHelper.createTintedDrawable(
activity,
R.drawable.ic_favorite_white_24dp,
ThemeStore.accentColor(activity)
)
else TintHelper.createTintedDrawable(
activity,
R.drawable.ic_playlist_play_white_24dp,
ATHUtil.resolveColor(activity, R.attr.colorControlNormal)
)
}
override fun getItemViewType(position: Int): Int {
return if (dataSet[position] is AbsSmartPlaylist) SMART_PLAYLIST else DEFAULT_PLAYLIST
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun getIdentifier(position: Int): Playlist? {
return dataSet[position]
}
override fun getName(playlist: Playlist): String {
return playlist.name
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Playlist>) {
when (menuItem.itemId) {
else -> SongsMenuHelper.handleMenuClick(
activity,
getSongList(selection),
menuItem.itemId
)
}
}
private fun getSongList(playlists: List<Playlist>): ArrayList<Song> {
val songs = ArrayList<Song>()
for (playlist in playlists) {
if (playlist is AbsCustomPlaylist) {
songs.addAll(playlist.getSongs(activity))
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}
}
return songs
}
private fun getSongs(playlist: Playlist): ArrayList<Song> {
val songs = ArrayList<Song>()
if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs(activity))
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}
return songs
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
init {
image?.apply {
val iconPadding =
activity.resources.getDimensionPixelSize(R.dimen.list_item_image_icon_padding)
setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
}
menu?.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_item_playlist)
popupMenu.setOnMenuItemClickListener { item ->
PlaylistMenuHelper.handleMenuClick(
activity, dataSet[layoutPosition], item
)
}
popupMenu.show()
}
imageTextContainer?.apply {
cardElevation = 0f
setCardBackgroundColor(Color.TRANSPARENT)
}
}
override fun onClick(v: View?) {
if (isInQuickSelectMode) {
toggleChecked(layoutPosition)
} else {
val playlist = dataSet[layoutPosition]
NavigationUtil.goToPlaylistNew(activity, playlist)
}
}
override fun onLongClick(v: View?): Boolean {
toggleChecked(layoutPosition)
return true
}
}
class PlaylistBitmapLoader(
private var adapter: PlaylistAdapter,
private var viewHolder: ViewHolder,
private var playlist: Playlist
) : AsyncTask<Void, Void, Bitmap>() {
override fun doInBackground(vararg params: Void?): Bitmap {
val songs = PlaylistSongsLoader.getPlaylistSongList(adapter.activity, playlist)
return AutoGeneratedPlaylistBitmap.getBitmap(adapter.activity, songs, false, false)
}
override fun onPostExecute(result: Bitmap?) {
super.onPostExecute(result)
viewHolder.image?.setImageBitmap(result)
val color = RetroColorUtil.getColor(
RetroColorUtil.generatePalette(
result
),
ATHUtil.resolveColor(adapter.activity, R.attr.colorSurface)
)
viewHolder.paletteColorContainer?.setBackgroundColor(color)
}
}
companion object {
val TAG: String = PlaylistAdapter::class.java.simpleName
private const val SMART_PLAYLIST = 0
private const val DEFAULT_PLAYLIST = 1
}
}

View file

@ -1,79 +0,0 @@
package code.name.monkey.retromusic.adapter.song
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Song
abstract class AbsOffsetSongAdapter(
activity: AppCompatActivity,
dataSet: MutableList<Song>,
@LayoutRes itemLayoutRes: Int,
cabHolder: CabHolder?
) : SongAdapter(activity, dataSet, itemLayoutRes, cabHolder) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongAdapter.ViewHolder {
if (viewType == OFFSET_ITEM) {
val view = LayoutInflater.from(activity)
.inflate(R.layout.item_list_quick_actions, parent, false)
return createViewHolder(view)
}
return super.onCreateViewHolder(parent, viewType)
}
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
override fun getItemId(position: Int): Long {
var positionFinal = position
positionFinal--
return if (positionFinal < 0) -2 else super.getItemId(positionFinal)
}
override fun getIdentifier(position: Int): Song? {
var positionFinal = position
positionFinal--
return if (positionFinal < 0) null else super.getIdentifier(positionFinal)
}
override fun getItemCount(): Int {
val superItemCount = super.getItemCount()
return if (superItemCount == 0) 0 else superItemCount + 1
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) OFFSET_ITEM else SONG
}
open inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
override // could also return null, just to be safe return empty song
val song: Song
get() = if (itemViewType == OFFSET_ITEM) Song.emptySong else dataSet[layoutPosition - 1]
override fun onClick(v: View?) {
if (isInQuickSelectMode && itemViewType != OFFSET_ITEM) {
toggleChecked(layoutPosition)
} else {
MusicPlayerRemote.openQueue(dataSet, layoutPosition - 1, true)
}
}
override fun onLongClick(v: View?): Boolean {
if (itemViewType == OFFSET_ITEM) return false
toggleChecked(layoutPosition)
return true
}
}
companion object {
const val OFFSET_ITEM = 0
const val SONG = 1
}
}

View file

@ -1,139 +0,0 @@
package code.name.monkey.retromusic.adapter.song
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.menu
import code.name.monkey.retromusic.dialogs.RemoveFromPlaylistDialog
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.PlaylistSong
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ViewUtil
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemViewHolder
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
class OrderablePlaylistSongAdapter(
activity: AppCompatActivity,
dataSet: ArrayList<Song>,
itemLayoutRes: Int,
cabHolder: CabHolder?,
private val onMoveItemListener: OnMoveItemListener?
) : PlaylistSongAdapter(
activity, dataSet, itemLayoutRes, cabHolder
), DraggableItemAdapter<OrderablePlaylistSongAdapter.ViewHolder> {
init {
setMultiSelectMenuRes(menu.menu_playlists_songs_selection)
}
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
override fun getItemId(position: Int): Long {
var positionFinal = position
positionFinal--
var long: Long = 0
if (positionFinal < 0) {
long = -2
} else {
if (dataSet[positionFinal] is PlaylistSong) {
long = (dataSet[positionFinal] as PlaylistSong).idInPlayList.toLong()
}
}
return long
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Song>) {
when (menuItem.itemId) {
R.id.action_remove_from_playlist -> {
RemoveFromPlaylistDialog.create(selection as ArrayList<PlaylistSong>)
.show(activity.supportFragmentManager, "ADD_PLAYLIST")
return
}
}
super.onMultipleItemAction(menuItem, selection)
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
return onMoveItemListener != null && position > 0 && (ViewUtil.hitTest(
holder.dragView!!, x, y
) || ViewUtil.hitTest(holder.image!!, x, y))
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange {
return ItemDraggableRange(1, dataSet.size)
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
if (onMoveItemListener != null && fromPosition != toPosition) {
onMoveItemListener.onMoveItem(fromPosition - 1, toPosition - 1)
}
}
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
return dropPosition > 0
}
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
interface OnMoveItemListener {
fun onMoveItem(fromPosition: Int, toPosition: Int)
}
inner class ViewHolder(itemView: View) : PlaylistSongAdapter.ViewHolder(itemView),
DraggableItemViewHolder {
@DraggableItemStateFlags
private var mDragStateFlags: Int = 0
override var songMenuRes: Int
get() = R.menu.menu_item_playlist_song
set(value) {
super.songMenuRes = value
}
init {
if (dragView != null) {
if (onMoveItemListener != null) {
dragView?.visibility = View.VISIBLE
} else {
dragView?.visibility = View.GONE
}
}
}
override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_remove_from_playlist -> {
RemoveFromPlaylistDialog.create(song as PlaylistSong)
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return true
}
}
return super.onSongMenuItemClick(item)
}
@DraggableItemStateFlags
override fun getDragStateFlags(): Int {
return mDragStateFlags
}
override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) {
mDragStateFlags = flags
}
}
companion object {
val TAG: String = OrderablePlaylistSongAdapter::class.java.simpleName
}
}

View file

@ -1,217 +0,0 @@
package code.name.monkey.retromusic.adapter.song
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicPlayerRemote.isPlaying
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playNextSong
import code.name.monkey.retromusic.helper.MusicPlayerRemote.removeFromQueue
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.ViewUtil
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange
import com.h6ah4i.android.widget.advrecyclerview.draggable.annotation.DraggableItemStateFlags
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem
import com.h6ah4i.android.widget.advrecyclerview.swipeable.annotation.SwipeableItemResults
import me.zhanghai.android.fastscroll.PopupTextProvider
class PlayingQueueAdapter(
activity: AppCompatActivity,
dataSet: MutableList<Song>,
private var current: Int,
itemLayoutRes: Int
) : SongAdapter(
activity, dataSet, itemLayoutRes, null
), DraggableItemAdapter<PlayingQueueAdapter.ViewHolder>,
SwipeableItemAdapter<PlayingQueueAdapter.ViewHolder>,
PopupTextProvider {
private var songToRemove: Song? = null
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
holder.imageText?.text = (position - current).toString()
holder.time?.text = MusicUtil.getReadableDurationString(dataSet[position].duration)
if (holder.itemViewType == HISTORY || holder.itemViewType == CURRENT) {
setAlpha(holder, 0.5f)
}
}
override fun getItemViewType(position: Int): Int {
if (position < current) {
return HISTORY
} else if (position > current) {
return UP_NEXT
}
return CURRENT
}
override fun loadAlbumCover(song: Song, holder: SongAdapter.ViewHolder) {
// We don't want to load it in this adapter
}
fun swapDataSet(dataSet: List<Song>, position: Int) {
this.dataSet = dataSet.toMutableList()
current = position
notifyDataSetChanged()
}
fun setCurrent(current: Int) {
this.current = current
notifyDataSetChanged()
}
private fun setAlpha(holder: SongAdapter.ViewHolder, alpha: Float) {
holder.image?.alpha = alpha
holder.title?.alpha = alpha
holder.text?.alpha = alpha
holder.imageText?.alpha = alpha
holder.paletteColorContainer?.alpha = alpha
holder.dragView?.alpha = alpha
holder.menu?.alpha = alpha
}
override fun getPopupText(position: Int): String {
return MusicUtil.getSectionName(dataSet[position].title)
}
override fun onCheckCanStartDrag(holder: ViewHolder, position: Int, x: Int, y: Int): Boolean {
return ViewUtil.hitTest(holder.imageText!!, x, y) || ViewUtil.hitTest(
holder.dragView!!,
x,
y
)
}
override fun onGetItemDraggableRange(holder: ViewHolder, position: Int): ItemDraggableRange? {
return null
}
override fun onMoveItem(fromPosition: Int, toPosition: Int) {
MusicPlayerRemote.moveSong(fromPosition, toPosition)
}
override fun onCheckCanDrop(draggingPosition: Int, dropPosition: Int): Boolean {
return true
}
override fun onItemDragStarted(position: Int) {
notifyDataSetChanged()
}
override fun onItemDragFinished(fromPosition: Int, toPosition: Int, result: Boolean) {
notifyDataSetChanged()
}
fun setSongToRemove(song: Song) {
songToRemove = song
}
inner class ViewHolder(itemView: View) : SongAdapter.ViewHolder(itemView) {
@DraggableItemStateFlags
private var mDragStateFlags: Int = 0
override var songMenuRes: Int
get() = R.menu.menu_item_playing_queue_song
set(value) {
super.songMenuRes = value
}
init {
imageText?.visibility = View.VISIBLE
dragView?.visibility = View.VISIBLE
}
override fun onSongMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_remove_from_playing_queue -> {
removeFromQueue(layoutPosition)
return true
}
}
return super.onSongMenuItemClick(item)
}
@DraggableItemStateFlags
override fun getDragStateFlags(): Int {
return mDragStateFlags
}
override fun setDragStateFlags(@DraggableItemStateFlags flags: Int) {
mDragStateFlags = flags
}
override fun getSwipeableContainerView(): View? {
return dummyContainer
}
}
companion object {
private const val HISTORY = 0
private const val CURRENT = 1
private const val UP_NEXT = 2
}
override fun onSwipeItem(
holder: ViewHolder?,
position: Int, @SwipeableItemResults result: Int
): SwipeResultAction {
return if (result === SwipeableItemConstants.RESULT_CANCELED) {
SwipeResultActionDefault()
} else {
SwipedResultActionRemoveItem(this, position, activity)
}
}
override fun onGetSwipeReactionType(holder: ViewHolder?, position: Int, x: Int, y: Int): Int {
return if (onCheckCanStartDrag(holder!!, position, x, y)) {
SwipeableItemConstants.REACTION_CAN_NOT_SWIPE_BOTH_H
} else {
SwipeableItemConstants.REACTION_CAN_SWIPE_BOTH_H
}
}
override fun onSwipeItemStarted(p0: ViewHolder?, p1: Int) {
}
override fun onSetSwipeBackground(holder: ViewHolder?, position: Int, result: Int) {
}
internal class SwipedResultActionRemoveItem(
private val adapter: PlayingQueueAdapter,
private val position: Int,
private val activity: AppCompatActivity
) : SwipeResultActionRemoveItem() {
private var songToRemove: Song? = null
private val isPlaying: Boolean = MusicPlayerRemote.isPlaying
private val songProgressMillis = 0
override fun onPerformAction() {
//currentlyShownSnackbar = null
}
override fun onSlideAnimationEnd() {
//initializeSnackBar(adapter, position, activity, isPlaying)
songToRemove = adapter.dataSet[position]
//If song removed was the playing song, then play the next song
if (isPlaying(songToRemove!!)) {
playNextSong()
}
//Swipe animation is much smoother when we do the heavy lifting after it's completed
adapter.setSongToRemove(songToRemove!!)
removeFromQueue(songToRemove!!)
}
}
}

View file

@ -1,75 +0,0 @@
package code.name.monkey.retromusic.adapter.song
import android.app.ActivityOptions
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.NavigationUtil
import com.google.android.material.button.MaterialButton
open class PlaylistSongAdapter(
activity: AppCompatActivity,
dataSet: MutableList<Song>,
itemLayoutRes: Int,
cabHolder: CabHolder?
) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, cabHolder) {
init {
this.setMultiSelectMenuRes(R.menu.menu_cannot_delete_single_songs_playlist_songs_selection)
}
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (holder.itemViewType == OFFSET_ITEM) {
val viewHolder = holder as ViewHolder
viewHolder.playAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openQueue(dataSet, 0, true)
}
}
viewHolder.shuffleAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
}
}
} else {
super.onBindViewHolder(holder, position - 1)
}
}
open inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) {
val playAction: MaterialButton? = itemView.findViewById(R.id.playAction)
val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction)
override var songMenuRes: Int
get() = R.menu.menu_item_cannot_delete_single_songs_playlist_song
set(value) {
super.songMenuRes = value
}
override fun onSongMenuItemClick(item: MenuItem): Boolean {
if (item.itemId == R.id.action_go_to_album) {
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
activity,
imageContainerCard ?: image,
activity.getString(R.string.transition_album_art)
)
NavigationUtil.goToAlbumOptions(activity, song.albumId, activityOptions)
return true
}
return super.onSongMenuItemClick(item)
}
}
companion object {
val TAG: String = PlaylistSongAdapter::class.java.simpleName
}
}

View file

@ -1,52 +0,0 @@
package code.name.monkey.retromusic.adapter.song
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Song
import com.google.android.material.button.MaterialButton
class ShuffleButtonSongAdapter(
activity: AppCompatActivity,
dataSet: MutableList<Song>,
itemLayoutRes: Int,
cabHolder: CabHolder?
) : AbsOffsetSongAdapter(activity, dataSet, itemLayoutRes, cabHolder) {
override fun createViewHolder(view: View): SongAdapter.ViewHolder {
return ViewHolder(view)
}
override fun onBindViewHolder(holder: SongAdapter.ViewHolder, position: Int) {
if (holder.itemViewType == OFFSET_ITEM) {
val viewHolder = holder as ViewHolder
viewHolder.playAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openQueue(dataSet, 0, true)
}
}
viewHolder.shuffleAction?.let {
it.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
}
}
} else {
super.onBindViewHolder(holder, position - 1)
}
}
inner class ViewHolder(itemView: View) : AbsOffsetSongAdapter.ViewHolder(itemView) {
val playAction: MaterialButton? = itemView.findViewById(R.id.playAction)
val shuffleAction: MaterialButton? = itemView.findViewById(R.id.shuffleAction)
override fun onClick(v: View?) {
if (itemViewType == OFFSET_ITEM) {
MusicPlayerRemote.openAndShuffleQueue(dataSet, true)
return
}
super.onClick(v)
}
}
}

View file

@ -1,38 +0,0 @@
package code.name.monkey.retromusic.adapter.song
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import java.util.*
class SimpleSongAdapter(
context: AppCompatActivity,
songs: ArrayList<Song>,
layoutRes: Int,
cabHolder: CabHolder?
) : SongAdapter(context, songs, layoutRes, cabHolder) {
override fun swapDataSet(dataSet: List<Song>) {
this.dataSet = dataSet.toMutableList()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
val fixedTrackNumber = MusicUtil.getFixedTrackNumber(dataSet[position].trackNumber)
holder.imageText?.text = if (fixedTrackNumber > 0) fixedTrackNumber.toString() else "-"
holder.time?.text = MusicUtil.getReadableDurationString(dataSet[position].duration)
}
override fun getItemCount(): Int {
return dataSet.size
}
}

View file

@ -1,216 +0,0 @@
package code.name.monkey.retromusic.adapter.song
import android.app.ActivityOptions
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.Song
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.color.MediaNotificationProcessor
import com.afollestad.materialcab.MaterialCab
import com.bumptech.glide.Glide
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.util.*
/**
* Created by hemanths on 13/08/17.
*/
open class SongAdapter(
protected val activity: AppCompatActivity,
var dataSet: MutableList<Song>,
protected var itemLayoutRes: Int,
cabHolder: CabHolder?,
showSectionName: Boolean = true
) : AbsMultiSelectAdapter<SongAdapter.ViewHolder, Song>(
activity,
cabHolder,
R.menu.menu_media_selection
), MaterialCab.Callback, PopupTextProvider {
private var showSectionName = true
init {
this.showSectionName = showSectionName
this.setHasStableIds(true)
}
open fun swapDataSet(dataSet: List<Song>) {
this.dataSet = dataSet.toMutableList()
notifyDataSetChanged()
}
override fun getItemId(position: Int): Long {
return dataSet[position].id.toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
try {
LayoutInflater.from(activity).inflate(itemLayoutRes, parent, false)
} catch (e: Resources.NotFoundException) {
LayoutInflater.from(activity).inflate(R.layout.item_list, parent, false)
}
return createViewHolder(view)
}
protected open fun createViewHolder(view: View): ViewHolder {
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val song = dataSet[position]
val isChecked = isChecked(song)
holder.itemView.isActivated = isChecked
if (isChecked) {
holder.menu?.hide()
} else {
holder.menu?.show()
}
holder.title?.text = getSongTitle(song)
holder.text?.text = getSongText(song)
loadAlbumCover(song, holder)
}
private fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
if (holder.paletteColorContainer != null) {
holder.title?.setTextColor(color.primaryTextColor)
holder.text?.setTextColor(color.secondaryTextColor)
holder.paletteColorContainer?.setBackgroundColor(color.backgroundColor)
}
holder.mask?.backgroundTintList = ColorStateList.valueOf(color.primaryTextColor)
}
protected open fun loadAlbumCover(song: Song, holder: ViewHolder) {
if (holder.image == null) {
return
}
SongGlideRequest.Builder.from(Glide.with(activity), song)
.checkIgnoreMediaStore(activity)
.generatePalette(activity).build()
.into(object : RetroMusicColoredTarget(holder.image!!) {
override fun onLoadCleared(placeholder: Drawable?) {
super.onLoadCleared(placeholder)
}
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(colors, holder)
}
})
}
private fun getSongTitle(song: Song): String? {
return song.title
}
private fun getSongText(song: Song): String? {
return song.artistName
}
override fun getItemCount(): Int {
return dataSet.size
}
override fun getIdentifier(position: Int): Song? {
return dataSet[position]
}
override fun getName(song: Song): String {
return song.title
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Song>) {
SongsMenuHelper.handleMenuClick(activity, selection, menuItem.itemId)
}
override fun getPopupText(position: Int): String {
val sectionName: String? = when (PreferenceUtil.songSortOrder) {
SortOrder.SongSortOrder.SONG_A_Z, SortOrder.SongSortOrder.SONG_Z_A -> dataSet[position].title
SortOrder.SongSortOrder.SONG_ALBUM -> dataSet[position].albumName
SortOrder.SongSortOrder.SONG_ARTIST -> dataSet[position].artistName
SortOrder.SongSortOrder.SONG_YEAR -> return MusicUtil.getYearString(dataSet[position].year)
SortOrder.SongSortOrder.COMPOSER -> dataSet[position].composer
else -> {
return ""
}
}
return MusicUtil.getSectionName(sectionName)
}
open inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
protected open var songMenuRes = SongMenuHelper.MENU_RES
protected open val song: Song
get() = dataSet[layoutPosition]
init {
setImageTransitionName(activity.getString(R.string.transition_album_art))
menu?.setOnClickListener(object : SongMenuHelper.OnClickSongMenu(activity) {
override val song: Song
get() = this@ViewHolder.song
override val menuRes: Int
get() = songMenuRes
override fun onMenuItemClick(item: MenuItem): Boolean {
return onSongMenuItemClick(item) || super.onMenuItemClick(item)
}
})
}
protected open fun onSongMenuItemClick(item: MenuItem): Boolean {
if (image != null && image!!.visibility == View.VISIBLE) {
when (item.itemId) {
R.id.action_go_to_album -> {
val activityOptions = ActivityOptions.makeSceneTransitionAnimation(
activity,
imageContainerCard ?: image,
activity.getString(R.string.transition_album_art)
)
NavigationUtil.goToAlbumOptions(activity, song.albumId, activityOptions)
return true
}
}
}
return false
}
override fun onClick(v: View?) {
if (isInQuickSelectMode) {
toggleChecked(layoutPosition)
} else {
MusicPlayerRemote.openQueue(dataSet, layoutPosition, true)
}
}
override fun onLongClick(v: View?): Boolean {
return toggleChecked(layoutPosition)
}
}
companion object {
val TAG: String = SongAdapter::class.java.simpleName
}
}

View file

@ -1,87 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.util.TypedValue
import androidx.annotation.RequiresApi
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.RetroUtil
@RequiresApi(Build.VERSION_CODES.N_MR1)
object AppShortcutIconGenerator {
fun generateThemedIcon(context: Context, iconId: Int): Icon {
return if (PreferenceUtil.isColoredAppShortcuts) {
generateUserThemedIcon(context, iconId)
} else {
generateDefaultThemedIcon(context, iconId)
}
}
private fun generateDefaultThemedIcon(context: Context, iconId: Int): Icon {
// Return an Icon of iconId with default colors
return generateThemedIcon(
context,
iconId,
context.getColor(R.color.app_shortcut_default_foreground),
context.getColor(R.color.app_shortcut_default_background)
)
}
private fun generateUserThemedIcon(context: Context, iconId: Int): Icon {
// Get background color from context's theme
val typedColorBackground = TypedValue()
context.theme.resolveAttribute(android.R.attr.colorBackground, typedColorBackground, true)
// Return an Icon of iconId with those colors
return generateThemedIcon(
context, iconId, ThemeStore.accentColor(context), typedColorBackground.data
)
}
private fun generateThemedIcon(
context: Context, iconId: Int, foregroundColor: Int, backgroundColor: Int
): Icon {
// Get and tint foreground and background drawables
val vectorDrawable = RetroUtil.getTintedVectorDrawable(context, iconId, foregroundColor)
val backgroundDrawable = RetroUtil.getTintedVectorDrawable(
context, R.drawable.ic_app_shortcut_background, backgroundColor
)
// Squash the two drawables together
val layerDrawable = LayerDrawable(arrayOf(backgroundDrawable, vectorDrawable))
// Return as an Icon
return Icon.createWithBitmap(drawableToBitmap(layerDrawable))
}
private fun drawableToBitmap(drawable: Drawable): Bitmap {
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
}

View file

@ -1,93 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import code.name.monkey.retromusic.activities.SearchActivity
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.SearchShortCutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist
import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
class AppShortcutLauncherActivity : Activity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var shortcutType = SHORTCUT_TYPE_NONE
// Set shortcutType from the intent extras
val extras = intent.extras
if (extras != null) {
shortcutType = extras.getInt(KEY_SHORTCUT_TYPE, SHORTCUT_TYPE_NONE)
}
when (shortcutType) {
SHORTCUT_TYPE_SHUFFLE_ALL -> {
startServiceWithPlaylist(
MusicService.SHUFFLE_MODE_SHUFFLE, ShuffleAllPlaylist(applicationContext)
)
DynamicShortcutManager.reportShortcutUsed(this, ShuffleAllShortcutType.id)
}
SHORTCUT_TYPE_TOP_TRACKS -> {
startServiceWithPlaylist(
MusicService.SHUFFLE_MODE_NONE, MyTopTracksPlaylist(applicationContext)
)
DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.id)
}
SHORTCUT_TYPE_LAST_ADDED -> {
startServiceWithPlaylist(
MusicService.SHUFFLE_MODE_NONE, LastAddedPlaylist(applicationContext)
)
DynamicShortcutManager.reportShortcutUsed(this, LastAddedShortcutType.id)
}
SHORTCUT_TYPE_SEARCH -> {
startActivity(Intent(this, SearchActivity::class.java))
DynamicShortcutManager.reportShortcutUsed(this, SearchShortCutType.id)
}
}
finish()
}
private fun startServiceWithPlaylist(shuffleMode: Int, playlist: Playlist) {
val intent = Intent(this, MusicService::class.java)
intent.action = ACTION_PLAY_PLAYLIST
val bundle = Bundle()
bundle.putParcelable(INTENT_EXTRA_PLAYLIST, playlist)
bundle.putInt(INTENT_EXTRA_SHUFFLE_MODE, shuffleMode)
intent.putExtras(bundle)
startService(intent)
}
companion object {
const val KEY_SHORTCUT_TYPE = "code.name.monkey.retromusic.appshortcuts.ShortcutType"
const val SHORTCUT_TYPE_SHUFFLE_ALL = 0
const val SHORTCUT_TYPE_TOP_TRACKS = 1
const val SHORTCUT_TYPE_LAST_ADDED = 2
const val SHORTCUT_TYPE_SEARCH = 3
const val SHORTCUT_TYPE_NONE = 4
}
}

View file

@ -1,72 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts
import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import code.name.monkey.retromusic.appshortcuts.shortcuttype.LastAddedShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.SearchShortCutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutType
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
import java.util.*
@TargetApi(Build.VERSION_CODES.N_MR1)
class DynamicShortcutManager(private val context: Context) {
private val shortcutManager: ShortcutManager =
this.context.getSystemService(ShortcutManager::class.java)
private val defaultShortcuts: List<ShortcutInfo>
get() = Arrays.asList(
SearchShortCutType(context).shortcutInfo,
ShuffleAllShortcutType(context).shortcutInfo,
TopTracksShortcutType(context).shortcutInfo,
LastAddedShortcutType(context).shortcutInfo
)
fun initDynamicShortcuts() {
//if (shortcutManager.dynamicShortcuts.size == 0) {
shortcutManager.dynamicShortcuts = defaultShortcuts
//}
}
fun updateDynamicShortcuts() {
shortcutManager.updateShortcuts(defaultShortcuts)
}
companion object {
fun createShortcut(
context: Context,
id: String,
shortLabel: String,
longLabel: String,
icon: Icon,
intent: Intent
): ShortcutInfo {
return ShortcutInfo.Builder(context, id).setShortLabel(shortLabel)
.setLongLabel(longLabel).setIcon(icon).setIntent(intent).build()
}
fun reportShortcutUsed(context: Context, shortcutId: String) {
context.getSystemService(ShortcutManager::class.java).reportShortcutUsed(shortcutId)
}
}
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts.shortcuttype
import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.os.Build
import android.os.Bundle
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
@TargetApi(Build.VERSION_CODES.N_MR1)
abstract class BaseShortcutType(internal var context: Context) {
internal abstract val shortcutInfo: ShortcutInfo
/**
* Creates an Intent that will launch MainActivtiy and immediately play {@param songs} in either shuffle or normal mode
*
* @param shortcutType Describes the type of shortcut to create (ShuffleAll, TopTracks, custom playlist, etc.)
* @return
*/
internal fun getPlaySongsIntent(shortcutType: Int): Intent {
val intent = Intent(context, AppShortcutLauncherActivity::class.java)
intent.action = Intent.ACTION_VIEW
val b = Bundle()
b.putInt(AppShortcutLauncherActivity.KEY_SHORTCUT_TYPE, shortcutType)
intent.putExtras(b)
return intent
}
companion object {
internal const val ID_PREFIX = "code.name.monkey.retromusic.appshortcuts.id."
val id: String
get() = ID_PREFIX + "invalid"
}
}

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts.shortcuttype
import android.annotation.TargetApi
import android.content.Context
import android.content.pm.ShortcutInfo
import android.os.Build
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
@TargetApi(Build.VERSION_CODES.N_MR1)
class LastAddedShortcutType(context: Context) : BaseShortcutType(context) {
override val shortcutInfo: ShortcutInfo
get() = ShortcutInfo.Builder(
context,
id
).setShortLabel(context.getString(R.string.app_shortcut_last_added_short)).setLongLabel(
context.getString(R.string.app_shortcut_last_added_long)
).setIcon(
AppShortcutIconGenerator.generateThemedIcon(
context,
R.drawable.ic_app_shortcut_last_added
)
).setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_LAST_ADDED))
.build()
companion object {
val id: String
get() = BaseShortcutType.ID_PREFIX + "last_added"
}
}

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts.shortcuttype
import android.annotation.TargetApi
import android.content.Context
import android.content.pm.ShortcutInfo
import android.os.Build
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
@TargetApi(Build.VERSION_CODES.N_MR1)
class SearchShortCutType(context: Context) : BaseShortcutType(context) {
companion object {
val id: String
get() = BaseShortcutType.ID_PREFIX + "search"
}
override val shortcutInfo: ShortcutInfo
get() = ShortcutInfo.Builder(
context,
id
).setShortLabel(context.getString(R.string.action_search))
.setLongLabel(context.getString(R.string.search_hint)).setIcon(
AppShortcutIconGenerator.generateThemedIcon(
context,
R.drawable.ic_app_shortcut_search
)
).setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SEARCH)).build()
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts.shortcuttype
import android.annotation.TargetApi
import android.content.Context
import android.content.pm.ShortcutInfo
import android.os.Build
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
@TargetApi(Build.VERSION_CODES.N_MR1)
class ShuffleAllShortcutType(context: Context) : BaseShortcutType(context) {
override val shortcutInfo: ShortcutInfo
get() = ShortcutInfo.Builder(
context, id
).setShortLabel(context.getString(R.string.app_shortcut_shuffle_all_short)).setLongLabel(
context.getString(R.string.app_shortcut_shuffle_all_long)
).setIcon(
AppShortcutIconGenerator.generateThemedIcon(
context, R.drawable.ic_app_shortcut_shuffle_all
)
).setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_SHUFFLE_ALL))
.build()
companion object {
val id: String
get() = BaseShortcutType.ID_PREFIX + "shuffle_all"
}
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appshortcuts.shortcuttype
import android.annotation.TargetApi
import android.content.Context
import android.content.pm.ShortcutInfo
import android.os.Build
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.appshortcuts.AppShortcutIconGenerator
import code.name.monkey.retromusic.appshortcuts.AppShortcutLauncherActivity
@TargetApi(Build.VERSION_CODES.N_MR1)
class TopTracksShortcutType(context: Context) : BaseShortcutType(context) {
override val shortcutInfo: ShortcutInfo
get() = ShortcutInfo.Builder(
context, id
).setShortLabel(context.getString(R.string.app_shortcut_top_tracks_short)).setLongLabel(
context.getString(R.string.app_shortcut_top_tracks_long)
).setIcon(
AppShortcutIconGenerator.generateThemedIcon(
context, R.drawable.ic_app_shortcut_top_tracks
)
).setIntent(getPlaySongsIntent(AppShortcutLauncherActivity.SHORTCUT_TYPE_TOP_TRACKS))
.build()
companion object {
val id: String
get() = BaseShortcutType.ID_PREFIX + "top_tracks"
}
}

View file

@ -1,234 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
class AppWidgetBig : BaseAppWidget() {
private var target: Target<Bitmap>? = null // for cancellation
/**
* Initialize given widgets to default state, where we launch Music on default click and hide
* actions if service not running.
*/
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(
context.packageName, R.layout.app_widget_big
)
appWidgetView.setViewVisibility(
R.id.media_titles,
View.INVISIBLE
)
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_next_white_24dp,
MaterialValueHelper.getPrimaryTextColor(context, false)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_previous_white_24dp,
MaterialValueHelper.getPrimaryTextColor(context, false)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_play_arrow_white_32dp,
MaterialValueHelper.getPrimaryTextColor(context, false)
)!!, 1f
)
)
linkButtons(context, appWidgetView)
pushUpdate(context, appWidgetIds, appWidgetView)
}
/**
* Update all active widget instances by pushing changes
*/
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
val appWidgetView = RemoteViews(
service.packageName, R.layout.app_widget_big
)
val isPlaying = service.isPlaying
val song = service.currentSong
// Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
appWidgetView.setViewVisibility(
R.id.media_titles,
View.INVISIBLE
)
} else {
appWidgetView.setViewVisibility(
R.id.media_titles,
View.VISIBLE
)
appWidgetView.setTextViewText(R.id.title, song.title)
appWidgetView.setTextViewText(
R.id.text,
getSongArtistAndAlbum(song)
)
}
val primaryColor = MaterialValueHelper.getPrimaryTextColor(service, false)
// Set correct drawable for pause state
val playPauseRes =
if (isPlaying) R.drawable.ic_pause_white_24dp else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap(
RetroUtil.getTintedVectorDrawable(
service,
playPauseRes,
primaryColor
)!!, 1f
)
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap(
RetroUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_next_white_24dp,
primaryColor
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap(
RetroUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_previous_white_24dp,
primaryColor
)!!, 1f
)
)
// Link actions buttons to intents
linkButtons(service, appWidgetView)
// Load the album cover async and push the update on completion
val p = RetroUtil.getScreenSize(service)
val widgetImageSize = Math.min(p.x, p.y)
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(appContext), song)
.checkIgnoreMediaStore(appContext).asBitmap().build()
.into(object : SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) {
override fun onResourceReady(
resource: Bitmap,
glideAnimation: GlideAnimation<in Bitmap>
) {
update(resource)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
update(null)
}
private fun update(bitmap: Bitmap?) {
if (bitmap == null) {
appWidgetView.setImageViewResource(
R.id.image,
R.drawable.default_audio_art
)
} else {
appWidgetView.setImageViewBitmap(R.id.image, bitmap)
}
pushUpdate(appContext, appWidgetIds, appWidgetView)
}
})
}
}
/**
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
val action =
Intent(context, MainActivity::class.java).putExtra(MainActivity.EXPAND_PANEL, true)
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java)
// Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
// Previous track
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
// Play and pause
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
// Next track
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
}
companion object {
const val NAME: String = "app_widget_big"
private var mInstance: AppWidgetBig? = null
val instance: AppWidgetBig
@Synchronized get() {
if (mInstance == null) {
mInstance = AppWidgetBig()
}
return mInstance!!
}
}
}

View file

@ -1,260 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
class AppWidgetCard : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
/**
* Initialize given widgets to default state, where we launch Music on default click and hide
* actions if service not running.
*/
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_card)
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
val secondaryColor = MaterialValueHelper.getSecondaryTextColor(context, true)
appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_next_white_24dp,
secondaryColor
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_previous_white_24dp,
secondaryColor
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_play_arrow_white_32dp,
secondaryColor
)!!, 1f
)
)
linkButtons(context, appWidgetView)
pushUpdate(context, appWidgetIds, appWidgetView)
}
/**
* Update all active widget instances by pushing changes
*/
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_card)
val isPlaying = service.isPlaying
val song = service.currentSong
// Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
appWidgetView.setTextViewText(R.id.title, song.title)
appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
}
// Set correct drawable for pause state
val playPauseRes =
if (isPlaying) R.drawable.ic_pause_white_24dp else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap(
RetroUtil.getTintedVectorDrawable(
service,
playPauseRes,
MaterialValueHelper.getSecondaryTextColor(service, true)
)!!, 1f
)
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap(
RetroUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_next_white_24dp,
MaterialValueHelper.getSecondaryTextColor(service, true)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap(
RetroUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_previous_white_24dp,
MaterialValueHelper.getSecondaryTextColor(service, true)
)!!, 1f
)
)
// Link actions buttons to intents
linkButtons(service, appWidgetView)
if (imageSize == 0) {
imageSize =
service.resources.getDimensionPixelSize(code.name.monkey.retromusic.R.dimen.app_widget_card_image_size)
}
if (cardRadius == 0f) {
cardRadius =
service.resources.getDimension(code.name.monkey.retromusic.R.dimen.app_widget_card_radius)
}
// Load the album cover async and push the update on completion
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(service), song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
) {
val palette = resource.palette
update(
resource.bitmap, palette.getVibrantColor(
palette.getMutedColor(
MaterialValueHelper.getSecondaryTextColor(
service, true
)
)
)
)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}
private fun update(bitmap: Bitmap?, color: Int) {
// Set correct drawable for pause state
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, ImageUtil.createBitmap(
ImageUtil.getTintedVectorDrawable(
service, playPauseRes, color
)
)
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next, ImageUtil.createBitmap(
ImageUtil.getTintedVectorDrawable(
service, R.drawable.ic_skip_next_white_24dp, color
)
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, ImageUtil.createBitmap(
ImageUtil.getTintedVectorDrawable(
service, R.drawable.ic_skip_previous_white_24dp, color
)
)
)
val image = getAlbumArtDrawable(service.resources, bitmap)
val roundedBitmap = createRoundedBitmap(
image, imageSize, imageSize, cardRadius, 0F, cardRadius, 0F
)
appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
pushUpdate(service, appWidgetIds, appWidgetView)
}
})
}
}
/**
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
val action: Intent = Intent(context, MainActivity::class.java).putExtra("expand", true)
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java)
// Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
// Previous track
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
// Play and pause
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
// Next track
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
}
companion object {
const val NAME = "app_widget_card"
private var mInstance: AppWidgetCard? = null
private var imageSize = 0
private var cardRadius = 0f
val instance: AppWidgetCard
@Synchronized get() {
if (mInstance == null) {
mInstance = AppWidgetCard()
}
return mInstance!!
}
}
}

View file

@ -1,251 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
class AppWidgetClassic : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
/**
* Initialize given widgets to default state, where we launch Music on default click and hide
* actions if service not running.
*/
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_classic)
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap(
R.id.button_next,
createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_next_white_24dp,
MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_previous_white_24dp,
MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_play_arrow_white_32dp,
MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f
)
)
linkButtons(context, appWidgetView)
pushUpdate(context, appWidgetIds, appWidgetView)
}
/**
* Update all active widget instances by pushing changes
*/
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_classic)
val isPlaying = service.isPlaying
val song = service.currentSong
// Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
appWidgetView.setTextViewText(R.id.title, song.title)
appWidgetView.setTextViewText(R.id.text, getSongArtistAndAlbum(song))
}
// Link actions buttons to intents
linkButtons(service, appWidgetView)
if (imageSize == 0) {
imageSize =
service.resources.getDimensionPixelSize(R.dimen.app_widget_classic_image_size)
}
if (cardRadius == 0f) {
cardRadius = service.resources.getDimension(R.dimen.app_widget_card_radius)
}
// Load the album cover async and push the update on completion
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(service), song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
) {
val palette = resource.palette
update(
resource.bitmap,
palette.getVibrantColor(
palette.getMutedColor(
MaterialValueHelper.getSecondaryTextColor(
service,
true
)
)
)
)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
update(null, Color.WHITE)
}
private fun update(bitmap: Bitmap?, color: Int) {
// Set correct drawable for pause state
val playPauseRes =
if (isPlaying) R.drawable.ic_pause_white_24dp else R.drawable.ic_play_arrow_white_24dp
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
ImageUtil.createBitmap(
ImageUtil.getTintedVectorDrawable(
service,
playPauseRes,
color
)
)
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next,
ImageUtil.createBitmap(
ImageUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_next_white_24dp,
color
)
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
ImageUtil.createBitmap(
ImageUtil.getTintedVectorDrawable(
service,
R.drawable.ic_skip_previous_white_24dp,
color
)
)
)
val image = getAlbumArtDrawable(service.resources, bitmap)
val roundedBitmap =
createRoundedBitmap(
image,
imageSize,
imageSize,
cardRadius,
0F,
cardRadius,
0F
)
appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
pushUpdate(appContext, appWidgetIds, appWidgetView)
}
})
}
}
/**
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java)
// Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
// Previous track
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
// Play and pause
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
// Next track
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
}
companion object {
const val NAME = "app_widget_classic"
private var mInstance: AppWidgetClassic? = null
private var imageSize = 0
private var cardRadius = 0f
val instance: AppWidgetClassic
@Synchronized get() {
if (mInstance == null) {
mInstance = AppWidgetClassic()
}
return mInstance!!
}
}
}

View file

@ -1,235 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.glide.SongGlideRequest
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target
class AppWidgetSmall : BaseAppWidget() {
private var target: Target<BitmapPaletteWrapper>? = null // for cancellation
/**
* Initialize given widgets to default state, where we launch Music on default click and hide
* actions if service not running.
*/
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_small)
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
appWidgetView.setImageViewResource(R.id.image, R.drawable.default_audio_art)
appWidgetView.setImageViewBitmap(
R.id.button_next,
createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_next_white_24dp,
MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev,
createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_skip_previous_white_24dp,
MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause,
createBitmap(
RetroUtil.getTintedVectorDrawable(
context,
R.drawable.ic_play_arrow_white_32dp,
MaterialValueHelper.getSecondaryTextColor(context, true)
)!!, 1f
)
)
linkButtons(context, appWidgetView)
pushUpdate(context, appWidgetIds, appWidgetView)
}
/**
* Update all active widget instances by pushing changes
*/
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_small)
val isPlaying = service.isPlaying
val song = service.currentSong
// Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
if (TextUtils.isEmpty(song.title) || TextUtils.isEmpty(song.artistName)) {
appWidgetView.setTextViewText(R.id.text_separator, "")
} else {
appWidgetView.setTextViewText(R.id.text_separator, "")
}
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
appWidgetView.setTextViewText(R.id.title, song.title)
appWidgetView.setTextViewText(R.id.text, song.artistName)
}
// Link actions buttons to intents
linkButtons(service, appWidgetView)
if (imageSize == 0) {
imageSize = service.resources.getDimensionPixelSize(R.dimen.app_widget_small_image_size)
}
if (cardRadius == 0f) {
cardRadius = service.resources.getDimension(R.dimen.app_widget_card_radius)
}
// Load the album cover async and push the update on completion
val appContext = service.applicationContext
service.runOnUiThread {
if (target != null) {
Glide.clear(target)
}
target = SongGlideRequest.Builder.from(Glide.with(service), song)
.checkIgnoreMediaStore(service).generatePalette(service).build().centerCrop()
.into(object : SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
override fun onResourceReady(
resource: BitmapPaletteWrapper,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>
) {
val palette = resource.palette
update(
resource.bitmap, palette.getVibrantColor(
palette.getMutedColor(
MaterialValueHelper.getSecondaryTextColor(
service, true
)
)
)
)
}
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
update(null, MaterialValueHelper.getSecondaryTextColor(service, true))
}
private fun update(bitmap: Bitmap?, color: Int) {
// Set correct drawable for pause state
val playPauseRes = if (isPlaying) R.drawable.ic_pause_white_24dp
else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap(
RetroUtil.getTintedVectorDrawable(
service, playPauseRes, color
)!!, 1f
)
)
// Set prev/next button drawables
appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap(
RetroUtil.getTintedVectorDrawable(
service, R.drawable.ic_skip_next_white_24dp, color
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap(
RetroUtil.getTintedVectorDrawable(
service, R.drawable.ic_skip_previous_white_24dp, color
)!!, 1f
)
)
val image = getAlbumArtDrawable(service.resources, bitmap)
val roundedBitmap = createRoundedBitmap(
image, imageSize, imageSize, cardRadius, 0f, 0f, 0f
)
appWidgetView.setImageViewBitmap(R.id.image, roundedBitmap)
pushUpdate(appContext, appWidgetIds, appWidgetView)
}
})
}
}
/**
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java)
// Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
// Previous track
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
// Play and pause
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
// Next track
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
}
companion object {
const val NAME: String = "app_widget_small"
private var mInstance: AppWidgetSmall? = null
private var imageSize = 0
private var cardRadius = 0f
val instance: AppWidgetSmall
@Synchronized get() {
if (mInstance == null) {
mInstance = AppWidgetSmall()
}
return mInstance!!
}
}
}

View file

@ -1,176 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appwidgets
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.text.TextUtils
import android.view.View
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.appwidgets.base.BaseAppWidget
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
import code.name.monkey.retromusic.util.RetroUtil
class AppWidgetText : BaseAppWidget() {
override fun defaultAppWidget(context: Context, appWidgetIds: IntArray) {
val appWidgetView = RemoteViews(context.packageName, R.layout.app_widget_text)
appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap(
RetroUtil.getTintedVectorDrawable(
context, R.drawable.ic_skip_next_white_24dp, ContextCompat.getColor(
context, R.color.md_white_1000
)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap(
RetroUtil.getTintedVectorDrawable(
context, R.drawable.ic_skip_previous_white_24dp, ContextCompat.getColor(
context, R.color.md_white_1000
)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap(
RetroUtil.getTintedVectorDrawable(
context, R.drawable.ic_play_arrow_white_32dp, ContextCompat.getColor(
context, R.color.md_white_1000
)
)!!, 1f
)
)
appWidgetView.setTextColor(
R.id.title, ContextCompat.getColor(context, R.color.md_white_1000)
)
appWidgetView.setTextColor(
R.id.text, ContextCompat.getColor(context, R.color.md_white_1000)
)
linkButtons(context, appWidgetView)
pushUpdate(context, appWidgetIds, appWidgetView)
}
/**
* Link up various button actions using [PendingIntent].
*/
private fun linkButtons(context: Context, views: RemoteViews) {
val action = Intent(context, MainActivity::class.java).putExtra("expand", true)
var pendingIntent: PendingIntent
val serviceName = ComponentName(context, MusicService::class.java)
// Home
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
pendingIntent = PendingIntent.getActivity(context, 0, action, 0)
views.setOnClickPendingIntent(R.id.image, pendingIntent)
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
// Previous track
pendingIntent = buildPendingIntent(context, ACTION_REWIND, serviceName)
views.setOnClickPendingIntent(R.id.button_prev, pendingIntent)
// Play and pause
pendingIntent = buildPendingIntent(context, ACTION_TOGGLE_PAUSE, serviceName)
views.setOnClickPendingIntent(R.id.button_toggle_play_pause, pendingIntent)
// Next track
pendingIntent = buildPendingIntent(context, ACTION_SKIP, serviceName)
views.setOnClickPendingIntent(R.id.button_next, pendingIntent)
}
override fun performUpdate(service: MusicService, appWidgetIds: IntArray?) {
val appWidgetView = RemoteViews(service.packageName, R.layout.app_widget_text)
val isPlaying = service.isPlaying
val song = service.currentSong
// Set the titles and artwork
if (TextUtils.isEmpty(song.title) && TextUtils.isEmpty(song.artistName)) {
appWidgetView.setViewVisibility(R.id.media_titles, View.INVISIBLE)
} else {
appWidgetView.setViewVisibility(R.id.media_titles, View.VISIBLE)
appWidgetView.setTextViewText(R.id.title, song.title)
appWidgetView.setTextViewText(R.id.text, song.artistName)
}
// Link actions buttons to intents
linkButtons(service, appWidgetView)
// Set correct drawable for pause state
val playPauseRes = if (isPlaying) R.drawable.ic_pause_white_24dp
else R.drawable.ic_play_arrow_white_32dp
appWidgetView.setImageViewBitmap(
R.id.button_toggle_play_pause, createBitmap(
RetroUtil.getTintedVectorDrawable(
App.getContext(), playPauseRes, ContextCompat.getColor(
App.getContext(), R.color.md_white_1000
)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_next, createBitmap(
RetroUtil.getTintedVectorDrawable(
App.getContext(),
R.drawable.ic_skip_next_white_24dp,
ContextCompat.getColor(
App.getContext(), R.color.md_white_1000
)
)!!, 1f
)
)
appWidgetView.setImageViewBitmap(
R.id.button_prev, createBitmap(
RetroUtil.getTintedVectorDrawable(
App.getContext(),
R.drawable.ic_skip_previous_white_24dp,
ContextCompat.getColor(
App.getContext(), R.color.md_white_1000
)
)!!, 1f
)
)
pushUpdate(service.applicationContext, appWidgetIds, appWidgetView)
}
companion object {
const val NAME: String = "app_widget_text"
private var mInstance: AppWidgetText? = null
val instance: AppWidgetText
@Synchronized get() {
if (mInstance == null) {
mInstance = AppWidgetText()
}
return mInstance!!
}
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appwidgets
import android.appwidget.AppWidgetManager
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import code.name.monkey.retromusic.service.MusicService
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val widgetManager = AppWidgetManager.getInstance(context)
// Start music service if there are any existing widgets
if (widgetManager.getAppWidgetIds(
ComponentName(
context, AppWidgetBig::class.java
)
).isNotEmpty() || widgetManager.getAppWidgetIds(
ComponentName(
context, AppWidgetClassic::class.java
)
).isNotEmpty() || widgetManager.getAppWidgetIds(
ComponentName(
context, AppWidgetSmall::class.java
)
).isNotEmpty() || widgetManager.getAppWidgetIds(
ComponentName(
context, AppWidgetCard::class.java
)
).isNotEmpty()
) {
val serviceIntent = Intent(context, MusicService::class.java)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // not allowed on Oreo
context.startService(serviceIntent)
}
}
}
}

View file

@ -1,189 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.appwidgets.base
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.TextUtils
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
abstract class BaseAppWidget : AppWidgetProvider() {
/**
* {@inheritDoc}
*/
override fun onUpdate(
context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
) {
defaultAppWidget(context, appWidgetIds)
val updateIntent = Intent(APP_WIDGET_UPDATE)
updateIntent.putExtra(EXTRA_APP_WIDGET_NAME, NAME)
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
updateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
context.sendBroadcast(updateIntent)
}
/**
* Handle a change notification coming over from [MusicService]
*/
fun notifyChange(service: MusicService, what: String) {
if (hasInstances(service)) {
if (META_CHANGED == what || PLAY_STATE_CHANGED == what) {
performUpdate(service, null)
}
}
}
protected fun pushUpdate(
context: Context, appWidgetIds: IntArray?, views: RemoteViews
) {
val appWidgetManager = AppWidgetManager.getInstance(context)
if (appWidgetIds != null) {
appWidgetManager.updateAppWidget(appWidgetIds, views)
} else {
appWidgetManager.updateAppWidget(ComponentName(context, javaClass), views)
}
}
/**
* Check against [AppWidgetManager] if there are any instances of this widget.
*/
private fun hasInstances(context: Context): Boolean {
val appWidgetManager = AppWidgetManager.getInstance(context)
val mAppWidgetIds = appWidgetManager.getAppWidgetIds(
ComponentName(
context, javaClass
)
)
return mAppWidgetIds.isNotEmpty()
}
protected fun buildPendingIntent(
context: Context, action: String, serviceName: ComponentName
): PendingIntent {
val intent = Intent(action)
intent.component = serviceName
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PendingIntent.getForegroundService(context, 0, intent, 0)
} else {
PendingIntent.getService(context, 0, intent, 0)
}
}
protected abstract fun defaultAppWidget(context: Context, appWidgetIds: IntArray)
abstract fun performUpdate(service: MusicService, appWidgetIds: IntArray?)
protected fun getAlbumArtDrawable(resources: Resources, bitmap: Bitmap?): Drawable {
return if (bitmap == null) {
ContextCompat.getDrawable(App.getContext(), R.drawable.default_audio_art)!!
} else {
BitmapDrawable(resources, bitmap)
}
}
protected fun getSongArtistAndAlbum(song: Song): String {
val builder = StringBuilder()
builder.append(song.artistName)
if (!TextUtils.isEmpty(song.artistName) && !TextUtils.isEmpty(song.albumName)) {
builder.append("")
}
builder.append(song.albumName)
return builder.toString()
}
companion object {
const val NAME: String = "app_widget"
fun createRoundedBitmap(
drawable: Drawable?,
width: Int,
height: Int,
tl: Float,
tr: Float,
bl: Float,
br: Float
): Bitmap? {
if (drawable == null) {
return null
}
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val c = Canvas(bitmap)
drawable.setBounds(0, 0, width, height)
drawable.draw(c)
val rounded = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(rounded)
val paint = Paint()
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.isAntiAlias = true
canvas.drawPath(
composeRoundedRectPath(
RectF(0f, 0f, width.toFloat(), height.toFloat()), tl, tr, bl, br
), paint
)
return rounded
}
fun createBitmap(drawable: Drawable, sizeMultiplier: Float): Bitmap {
val bitmap = Bitmap.createBitmap(
(drawable.intrinsicWidth * sizeMultiplier).toInt(),
(drawable.intrinsicHeight * sizeMultiplier).toInt(),
Bitmap.Config.ARGB_8888
)
val c = Canvas(bitmap)
drawable.setBounds(0, 0, c.width, c.height)
drawable.draw(c)
return bitmap
}
protected fun composeRoundedRectPath(
rect: RectF, tl: Float, tr: Float, bl: Float, br: Float
): Path {
val path = Path()
path.moveTo(rect.left + tl, rect.top)
path.lineTo(rect.right - tr, rect.top)
path.quadTo(rect.right, rect.top, rect.right, rect.top + tr)
path.lineTo(rect.right, rect.bottom - br)
path.quadTo(rect.right, rect.bottom, rect.right - br, rect.bottom)
path.lineTo(rect.left + bl, rect.bottom)
path.quadTo(rect.left, rect.bottom, rect.left, rect.bottom - bl)
path.lineTo(rect.left, rect.top + tl)
path.quadTo(rect.left, rect.top, rect.left + tl, rect.top)
path.close()
return path
}
}
}

View file

@ -1,63 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dagger
import code.name.monkey.retromusic.activities.artists.ArtistDetailActivity
import code.name.monkey.retromusic.activities.GenreDetailsActivity
import code.name.monkey.retromusic.activities.PlaylistDetailActivity
import code.name.monkey.retromusic.activities.SearchActivity
import code.name.monkey.retromusic.dagger.module.AppModule
import code.name.monkey.retromusic.dagger.module.PresenterModule
import code.name.monkey.retromusic.fragments.albums.AlbumsFragment
import code.name.monkey.retromusic.fragments.artists.ArtistsFragment
import code.name.monkey.retromusic.fragments.genres.GenresFragment
import code.name.monkey.retromusic.fragments.home.BannerHomeFragment
import code.name.monkey.retromusic.fragments.playlists.PlaylistsFragment
import code.name.monkey.retromusic.fragments.songs.SongsFragment
import dagger.Component
import javax.inject.Singleton
/**
* Created by hemanths on 2019-09-04.
*/
@Singleton
@Component(
modules = [
AppModule::class,
PresenterModule::class
]
)
interface MusicComponent {
fun inject(songsFragment: SongsFragment)
fun inject(albumsFragment: AlbumsFragment)
fun inject(artistsFragment: ArtistsFragment)
fun inject(genresFragment: GenresFragment)
fun inject(playlistsFragment: PlaylistsFragment)
fun inject(artistDetailActivity: ArtistDetailActivity)
fun inject(playlistDetailActivity: PlaylistDetailActivity)
fun inject(genreDetailsActivity: GenreDetailsActivity)
fun inject(searchActivity: SearchActivity)
fun inject(bannerHomeFragment: BannerHomeFragment)
}

View file

@ -1,33 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dagger.module
import android.app.Application
import android.content.Context
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by hemanths on 2019-09-04.
*/
@Module
class AppModule(private val application: Application) {
@Provides
@Singleton
fun provideContext(): Context = application
}

View file

@ -1,55 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dagger.module
import android.content.Context
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter
import code.name.monkey.retromusic.mvp.presenter.GenreDetailsPresenter.GenreDetailsPresenterImpl
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter
import code.name.monkey.retromusic.mvp.presenter.PlaylistSongsPresenter.PlaylistSongsPresenterImpl
import code.name.monkey.retromusic.mvp.presenter.SearchPresenter
import code.name.monkey.retromusic.mvp.presenter.SearchPresenter.SearchPresenterImpl
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.providers.interfaces.Repository
import dagger.Module
import dagger.Provides
/**
* Created by hemanths on 2019-12-30.
*/
@Module
class PresenterModule {
@Provides
fun providesRepository(context: Context): Repository {
return RepositoryImpl(context)
}
@Provides
fun providesGenreDetailsPresenter(presenter: GenreDetailsPresenterImpl): GenreDetailsPresenter {
return presenter
}
@Provides
fun providesPlaylistSongPresenter(presenter: PlaylistSongsPresenterImpl): PlaylistSongsPresenter {
return presenter
}
@Provides
fun providesSearchPresenter(presenter: SearchPresenterImpl): SearchPresenter {
return presenter
}
}

View file

@ -1,68 +0,0 @@
package code.name.monkey.retromusic.deezer
import android.content.Context
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
import retrofit2.http.GET
import retrofit2.http.Query
import java.io.File
import java.util.*
private const val BASE_QUERY_ARTIST = "search/artist"
private const val BASE_URL = "https://api.deezer.com/"
interface DeezerApiService {
@GET("$BASE_QUERY_ARTIST&limit=1")
fun getArtistImage(
@Query("q") artistName: String
): Call<DeezerResponse>
companion object {
operator fun invoke(
client: okhttp3.Call.Factory
): DeezerApiService {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.callFactory(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create()
}
fun createDefaultOkHttpClient(
context: Context
): OkHttpClient.Builder = OkHttpClient.Builder()
.cache(createDefaultCache(context))
.addInterceptor(createCacheControlInterceptor())
private fun createDefaultCache(
context: Context
): Cache? {
val cacheDir = File(context.applicationContext.cacheDir.absolutePath, "/okhttp-deezer/")
if (cacheDir.mkdir() or cacheDir.isDirectory) {
return Cache(cacheDir, 1024 * 1024 * 10)
}
return null
}
private fun createCacheControlInterceptor(): Interceptor {
return Interceptor { chain ->
val modifiedRequest = chain.request().newBuilder()
.addHeader(
"Cache-Control",
String.format(
Locale.getDefault(),
"max-age=31536000, max-stale=31536000"
)
).build()
chain.proceed(modifiedRequest)
}
}
}
}

View file

@ -1,31 +0,0 @@
package code.name.monkey.retromusic.deezer
import com.google.gson.annotations.SerializedName
data class Data(
val id: String,
val link: String,
val name: String,
@SerializedName("nb_album")
val nbAlbum: Int,
@SerializedName("nb_fan")
val nbFan: Int,
val picture: String,
@SerializedName("picture_big")
val pictureBig: String,
@SerializedName("picture_medium")
val pictureMedium: String,
@SerializedName("picture_small")
val pictureSmall: String,
@SerializedName("picture_xl")
val pictureXl: String,
val radio: Boolean,
val tracklist: String,
val type: String
)
data class DeezerResponse(
val data: List<Data>,
val next: String,
val total: Int
)

View file

@ -1,79 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.loaders.PlaylistLoader
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class AddToPlaylistDialog : DialogFragment() {
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
val playlists = PlaylistLoader.getAllPlaylists(requireContext())
val playlistNames = mutableListOf<CharSequence>()
playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist))
for (p in playlists) {
playlistNames.add(p.name)
}
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
)
.setTitle(R.string.add_playlist_title)
.setItems(playlistNames.toTypedArray()) { _, which ->
val songs = extraNotNull<ArrayList<Song>>(EXTRA_SONG).value
if (which == 0) {
CreatePlaylistDialog.create(songs)
.show(requireActivity().supportFragmentManager, "ADD_TO_PLAYLIST")
} else {
PlaylistsUtil.addToPlaylist(
requireContext(),
songs,
playlists[which - 1].id,
true
)
}
dismiss()
}
.create()
}
companion object {
fun create(song: Song): AddToPlaylistDialog {
val list = ArrayList<Song>()
list.add(song)
return create(list)
}
fun create(songs: List<Song>): AddToPlaylistDialog {
val dialog = AddToPlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, ArrayList(songs))
dialog.arguments = args
return dialog
}
}
}

View file

@ -1,157 +0,0 @@
package code.name.monkey.retromusic.dialogs;
import android.Manifest;
import android.app.Dialog;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.DialogFragment;
import com.afollestad.materialdialogs.MaterialDialog;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import code.name.monkey.retromusic.R;
public class BlacklistFolderChooserDialog extends DialogFragment implements MaterialDialog.ListCallback {
String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath();
private File parentFolder;
private File[] parentContents;
private boolean canGoUp = false;
private FolderCallback callback;
public static BlacklistFolderChooserDialog create() {
return new BlacklistFolderChooserDialog();
}
private String[] getContentsArray() {
if (parentContents == null) {
if (canGoUp) {
return new String[]{".."};
}
return new String[]{};
}
String[] results = new String[parentContents.length + (canGoUp ? 1 : 0)];
if (canGoUp) {
results[0] = "..";
}
for (int i = 0; i < parentContents.length; i++) {
results[canGoUp ? i + 1 : i] = parentContents[i].getName();
}
return results;
}
private File[] listFiles() {
File[] contents = parentFolder.listFiles();
List<File> results = new ArrayList<>();
if (contents != null) {
for (File fi : contents) {
if (fi.isDirectory()) {
results.add(fi);
}
}
Collections.sort(results, new FolderSorter());
return results.toArray(new File[results.size()]);
}
return null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& ActivityCompat.checkSelfPermission(
getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
return new MaterialDialog.Builder(getActivity())
.title(R.string.md_error_label)
.content(R.string.md_storage_perm_error)
.positiveText(android.R.string.ok)
.build();
}
if (savedInstanceState == null) {
savedInstanceState = new Bundle();
}
if (!savedInstanceState.containsKey("current_path")) {
savedInstanceState.putString("current_path", initialPath);
}
parentFolder = new File(savedInstanceState.getString("current_path", File.pathSeparator));
checkIfCanGoUp();
parentContents = listFiles();
MaterialDialog.Builder builder =
new MaterialDialog.Builder(getActivity())
.title(parentFolder.getAbsolutePath())
.items((CharSequence[]) getContentsArray())
.itemsCallback(this)
.autoDismiss(false)
.onPositive((dialog, which) -> {
dismiss();
callback.onFolderSelection(BlacklistFolderChooserDialog.this, parentFolder);
})
.onNegative((materialDialog, dialogAction) -> dismiss())
.positiveText(R.string.add_action)
.negativeText(android.R.string.cancel);
return builder.build();
}
@Override
public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence s) {
if (canGoUp && i == 0) {
parentFolder = parentFolder.getParentFile();
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = parentFolder.getParentFile();
}
checkIfCanGoUp();
} else {
parentFolder = parentContents[canGoUp ? i - 1 : i];
canGoUp = true;
if (parentFolder.getAbsolutePath().equals("/storage/emulated")) {
parentFolder = Environment.getExternalStorageDirectory();
}
}
reload();
}
private void checkIfCanGoUp() {
canGoUp = parentFolder.getParent() != null;
}
private void reload() {
parentContents = listFiles();
MaterialDialog dialog = (MaterialDialog) getDialog();
dialog.setTitle(parentFolder.getAbsolutePath());
dialog.setItems((CharSequence[]) getContentsArray());
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_path", parentFolder.getAbsolutePath());
}
public void setCallback(FolderCallback callback) {
this.callback = callback;
}
public interface FolderCallback {
void onFolderSelection(@NonNull BlacklistFolderChooserDialog dialog, @NonNull File folder);
}
private static class FolderSorter implements Comparator<File> {
@Override
public int compare(File lhs, File rhs) {
return lhs.getName().compareTo(rhs.getName());
}
}
}

View file

@ -1,90 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import androidx.fragment.app.DialogFragment
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.synthetic.main.dialog_playlist.view.*
class CreatePlaylistDialog : DialogFragment() {
@SuppressLint("InflateParams")
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
val view = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_playlist, null)
val playlistView: TextInputEditText = view.actionNewPlaylist
val playlistContainer: TextInputLayout = view.actionNewPlaylistContainer
MaterialUtil.setTint(playlistContainer, false)
return MaterialAlertDialogBuilder(
requireActivity(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
)
.setTitle(R.string.new_playlist_title)
.setView(view)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(
R.string.create_action
) { _, _ ->
val extra = extraNotNull<ArrayList<Song>>(EXTRA_SONG)
val playlistName = playlistView.text.toString()
if (!TextUtils.isEmpty(playlistName)) {
val playlistId = PlaylistsUtil.createPlaylist(
requireContext(),
playlistView.text.toString()
)
if (playlistId != -1) {
PlaylistsUtil.addToPlaylist(requireContext(), extra.value, playlistId, true)
}
}
}
.create()
}
companion object {
@JvmOverloads
@JvmStatic
fun create(song: Song? = null): CreatePlaylistDialog {
val list = ArrayList<Song>()
if (song != null) {
list.add(song)
}
return create(list)
}
@JvmStatic
fun create(songs: ArrayList<Song>): CreatePlaylistDialog {
val dialog = CreatePlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, songs)
dialog.arguments = args
return dialog
}
}
}

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_PLAYLIST
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.util.PlaylistsUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DeletePlaylistDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val playlists = extraNotNull<List<Playlist>>(EXTRA_PLAYLIST).value
val title: Int
val message: CharSequence
//noinspection ConstantConditions
if (playlists.size > 1) {
title = R.string.delete_playlists_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_x_playlists), playlists.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
} else {
title = R.string.delete_playlist_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_playlist_x), playlists[0].name),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
)
.setTitle(title)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ ->
PlaylistsUtil.deletePlaylists(requireContext(), playlists)
}
.create()
}
companion object {
fun create(playlist: Playlist): DeletePlaylistDialog {
val list = ArrayList<Playlist>()
list.add(playlist)
return create(list)
}
fun create(playlist: ArrayList<Playlist>): DeletePlaylistDialog {
val dialog = DeletePlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_PLAYLIST, playlist)
dialog.arguments = args
return dialog
}
}
}

View file

@ -1,145 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.util.SAFUtil;
/**
* Created by hemanths on 2019-07-31.
*/
public class DeleteSongsAsyncTask extends DialogAsyncTask<DeleteSongsAsyncTask.LoadingInfo, Integer, Void> {
private WeakReference<FragmentActivity> activityWeakReference;
private WeakReference<DeleteSongsDialog> dialogReference;
public DeleteSongsAsyncTask(@NonNull DeleteSongsDialog dialog) {
super(dialog.getActivity());
this.dialogReference = new WeakReference<>(dialog);
this.activityWeakReference = new WeakReference<>(dialog.getActivity());
}
@NonNull
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialAlertDialogBuilder(context,
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert)
.setTitle(R.string.deleting_songs)
.setView(R.layout.loading)
.setCancelable(false)
.create();
}
@Nullable
@Override
protected Void doInBackground(@NonNull LoadingInfo... loadingInfos) {
try {
LoadingInfo info = loadingInfos[0];
DeleteSongsDialog dialog = this.dialogReference.get();
FragmentActivity fragmentActivity = this.activityWeakReference.get();
if (dialog == null || fragmentActivity == null) {
return null;
}
if (!info.isIntent) {
if (!SAFUtil.isSAFRequiredForSongs(info.songs)) {
dialog.deleteSongs(info.songs, null);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (SAFUtil.isSDCardAccessGranted(fragmentActivity)) {
dialog.deleteSongs(info.songs, null);
} else {
dialog.startActivityForResult(new Intent(fragmentActivity, SAFGuideActivity.class),
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE);
}
} else {
Log.i("Hmm", "doInBackground: kitkat delete songs");
}
}
} else {
switch (info.requestCode) {
case SAFUtil.REQUEST_SAF_PICK_TREE:
if (info.resultCode == Activity.RESULT_OK) {
SAFUtil.saveTreeUri(fragmentActivity, info.intent);
if (dialog.songsToRemove != null) {
dialog.deleteSongs(dialog.songsToRemove, null);
}
}
break;
case SAFUtil.REQUEST_SAF_PICK_FILE:
if (info.resultCode == Activity.RESULT_OK) {
dialog.deleteSongs(Collections.singletonList(dialog.currentSong),
Collections.singletonList(info.intent.getData()));
}
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static class LoadingInfo {
public Intent intent;
public boolean isIntent;
public int requestCode;
public int resultCode;
public List<Uri> safUris;
public List<Song> songs;
public LoadingInfo(List<Song> songs, List<Uri> safUris) {
this.isIntent = false;
this.songs = songs;
this.safUris = safUris;
}
public LoadingInfo(int requestCode, int resultCode, Intent intent) {
this.isIntent = true;
this.requestCode = requestCode;
this.resultCode = resultCode;
this.intent = intent;
}
}
}

View file

@ -1,123 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.SAFUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DeleteSongsDialog : DialogFragment() {
@JvmField
var currentSong: Song? = null
@JvmField
var songsToRemove: List<Song>? = null
private var deleteSongsAsyncTask: DeleteSongsAsyncTask? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
var title = 0
var message: CharSequence = ""
if (songs.size > 1) {
title = R.string.delete_songs_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_x_songs), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
} else {
title = R.string.delete_song_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_song_x), songs[0].title),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
)
.setTitle(title)
.setMessage(message)
.setCancelable(false)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ ->
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
MusicPlayerRemote.playNextSong()
}
songsToRemove = songs
deleteSongsAsyncTask = DeleteSongsAsyncTask(this@DeleteSongsDialog)
deleteSongsAsyncTask?.execute(DeleteSongsAsyncTask.LoadingInfo(songs, null))
}
.create()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE,
SAFUtil.REQUEST_SAF_PICK_FILE -> {
if (deleteSongsAsyncTask != null) {
deleteSongsAsyncTask?.cancel(true)
}
deleteSongsAsyncTask = DeleteSongsAsyncTask(this)
deleteSongsAsyncTask?.execute(
DeleteSongsAsyncTask.LoadingInfo(
requestCode,
resultCode,
data
)
)
}
}
}
fun deleteSongs(songs: List<Song>, safUris: List<Uri>?) {
MusicUtil.deleteTracks(requireActivity(), songs, safUris) { this.dismiss() }
}
companion object {
fun create(song: Song): DeleteSongsDialog {
val list = ArrayList<Song>()
list.add(song)
return create(list)
}
fun create(songs: List<Song>): DeleteSongsDialog {
val dialog = DeleteSongsDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, ArrayList(songs))
dialog.arguments = args
return dialog
}
}
}

View file

@ -1,86 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.string
import code.name.monkey.retromusic.model.PlaylistSong
import code.name.monkey.retromusic.util.PlaylistsUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class RemoveFromPlaylistDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = requireArguments().getParcelableArrayList<PlaylistSong>(EXTRA_SONG)
var title = 0
var message: CharSequence = ""
if (songs != null) {
if (songs.size > 1) {
title = R.string.remove_songs_from_playlist_title
message = HtmlCompat.fromHtml(
String.format(getString(string.remove_x_songs_from_playlist), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
} else {
title = R.string.remove_song_from_playlist_title
message = HtmlCompat.fromHtml(
String.format(
getString(string.remove_song_x_from_playlist),
songs[0].title
),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
}
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
)
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.remove_action) { _, _ ->
PlaylistsUtil.removeFromPlaylist(
requireContext(),
songs as MutableList<PlaylistSong>
)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
companion object {
fun create(song: PlaylistSong): RemoveFromPlaylistDialog {
val list = ArrayList<PlaylistSong>()
list.add(song)
return create(list)
}
fun create(songs: ArrayList<PlaylistSong>): RemoveFromPlaylistDialog {
val dialog = RemoveFromPlaylistDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, songs)
dialog.arguments = args
return dialog
}
}
}

View file

@ -1,75 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.annotation.SuppressLint
import android.app.Dialog
import android.os.Bundle
import android.provider.MediaStore.Audio.Playlists.Members.PLAYLIST_ID
import android.view.LayoutInflater
import androidx.fragment.app.DialogFragment
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.util.PlaylistsUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
class RenamePlaylistDialog : DialogFragment() {
@SuppressLint("InflateParams")
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
val layout = LayoutInflater.from(requireContext())
.inflate(R.layout.dialog_playlist, null)
val inputEditText: TextInputEditText = layout.findViewById(R.id.actionNewPlaylist)
val nameContainer: TextInputLayout =
layout.findViewById(R.id.actionNewPlaylistContainer)
MaterialUtil.setTint(nameContainer, false)
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
)
.setTitle(R.string.rename_playlist_title)
.setView(layout)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_rename) { _, _ ->
val name = inputEditText.text.toString()
if (name.isNotEmpty()) {
PlaylistsUtil.renamePlaylist(
requireContext(),
extraNotNull<Long>(PLAYLIST_ID).value,
name
)
}
}
.create()
}
companion object {
fun create(playlistId: Long): RenamePlaylistDialog {
val dialog = RenamePlaylistDialog()
val args = Bundle()
args.putLong(PLAYLIST_ID, playlistId)
dialog.arguments = args
return dialog
}
}
}

View file

@ -1,177 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.Dialog
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.os.SystemClock
import android.view.LayoutInflater
import android.widget.CheckBox
import android.widget.SeekBar
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.addAccentColor
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.ACTION_PENDING_QUIT
import code.name.monkey.retromusic.service.MusicService.ACTION_QUIT
import code.name.monkey.retromusic.util.PreferenceUtil
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class SleepTimerDialog : DialogFragment() {
private var seekArcProgress: Int = 0
private lateinit var timerUpdater: TimerUpdater
private lateinit var materialDialog: MaterialDialog
private lateinit var shouldFinishLastSong: CheckBox
private lateinit var seekBar: SeekBar
private lateinit var timerDisplay: TextView
@SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
timerUpdater = TimerUpdater()
val layout = LayoutInflater.from(requireContext())
.inflate(R.layout.dialog_sleep_timer, null)
shouldFinishLastSong = layout.findViewById(R.id.shouldFinishLastSong)
seekBar = layout.findViewById(R.id.seekBar)
timerDisplay = layout.findViewById(R.id.timerDisplay)
val finishMusic = PreferenceUtil.isSleepTimerFinishMusic
shouldFinishLastSong.apply {
addAccentColor()
isChecked = finishMusic
}
seekBar.apply {
addAccentColor()
seekArcProgress = PreferenceUtil.lastSleepTimerValue
updateTimeDisplayTime()
seekBar.progress = seekArcProgress
}
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
if (i < 1) {
seekBar.progress = 1
return
}
seekArcProgress = i
updateTimeDisplayTime()
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
PreferenceUtil.lastSleepTimerValue = seekArcProgress
}
})
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
)
.setTitle(R.string.action_sleep_timer)
.setView(layout)
.setPositiveButton(R.string.action_set) { _, _ ->
PreferenceUtil.isSleepTimerFinishMusic = shouldFinishLastSong.isChecked
val minutes = seekArcProgress
val pi = makeTimerPendingIntent(PendingIntent.FLAG_CANCEL_CURRENT)
val nextSleepTimerElapsedTime = SystemClock.elapsedRealtime() + minutes * 60 * 1000
PreferenceUtil.nextSleepTimerElapsedRealTime = nextSleepTimerElapsedTime.toInt()
val am = requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextSleepTimerElapsedTime, pi)
Toast.makeText(
requireContext(),
requireContext().resources.getString(R.string.sleep_timer_set, minutes),
Toast.LENGTH_SHORT
).show()
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
val previous = makeTimerPendingIntent(PendingIntent.FLAG_NO_CREATE)
if (previous != null) {
val am =
requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
am.cancel(previous)
previous.cancel()
Toast.makeText(
requireContext(),
requireContext().resources.getString(R.string.sleep_timer_canceled),
Toast.LENGTH_SHORT
).show()
val musicService = MusicPlayerRemote.musicService
if (musicService != null && musicService.pendingQuit) {
musicService.pendingQuit = false
Toast.makeText(
requireContext(),
requireContext().resources.getString(R.string.sleep_timer_canceled),
Toast.LENGTH_SHORT
).show()
}
}
}
.create()
}
private fun updateTimeDisplayTime() {
timerDisplay.text = "$seekArcProgress min"
}
private fun makeTimerPendingIntent(flag: Int): PendingIntent? {
return PendingIntent.getService(requireActivity(), 0, makeTimerIntent(), flag)
}
private fun makeTimerIntent(): Intent {
val intent = Intent(requireActivity(), MusicService::class.java)
return if (shouldFinishLastSong.isChecked) {
intent.setAction(ACTION_PENDING_QUIT)
} else intent.setAction(ACTION_QUIT)
}
private fun updateCancelButton() {
val musicService = MusicPlayerRemote.musicService
if (musicService != null && musicService.pendingQuit) {
materialDialog.getActionButton(DialogAction.NEUTRAL).text =
materialDialog.context.getString(R.string.cancel_current_timer)
} else {
materialDialog.getActionButton(DialogAction.NEUTRAL).text = null
}
}
private inner class TimerUpdater internal constructor() :
CountDownTimer(
PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(),
1000
) {
override fun onTick(millisUntilFinished: Long) {
println("onTick: $millisUntilFinished")
seekBar.progress = millisUntilFinished.toInt()
}
override fun onFinish() {
updateCancelButton()
}
}
}

View file

@ -1,187 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Spanned
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.NonNull
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
import org.jaudiotagger.tag.TagException
import java.io.File
import java.io.IOException
inline fun ViewGroup.forEach(action: (View) -> Unit) {
for (i in 0 until childCount) {
action(getChildAt(i))
}
}
class SongDetailDialog : DialogFragment() {
@SuppressLint("InflateParams")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context: Context = requireContext()
val dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_file_details, null)
val song = requireArguments().getParcelable<Song>(EXTRA_SONG)
val fileName: TextView = dialogView.findViewById(R.id.fileName)
val filePath: TextView = dialogView.findViewById(R.id.filePath)
val fileSize: TextView = dialogView.findViewById(R.id.fileSize)
val fileFormat: TextView = dialogView.findViewById(R.id.fileFormat)
val trackLength: TextView = dialogView.findViewById(R.id.trackLength)
val bitRate: TextView = dialogView.findViewById(R.id.bitrate)
val samplingRate: TextView = dialogView.findViewById(R.id.samplingRate)
fileName.text = makeTextWithTitle(context, R.string.label_file_name, "-")
filePath.text = makeTextWithTitle(context, R.string.label_file_path, "-")
fileSize.text = makeTextWithTitle(context, R.string.label_file_size, "-")
fileFormat.text = makeTextWithTitle(context, R.string.label_file_format, "-")
trackLength.text = makeTextWithTitle(context, R.string.label_track_length, "-")
bitRate.text = makeTextWithTitle(context, R.string.label_bit_rate, "-")
samplingRate.text = makeTextWithTitle(context, R.string.label_sampling_rate, "-")
if (song != null) {
val songFile = File(song.data)
if (songFile.exists()) {
fileName.text = makeTextWithTitle(context, R.string.label_file_name, songFile.name)
filePath.text =
makeTextWithTitle(context, R.string.label_file_path, songFile.absolutePath)
fileSize.text =
makeTextWithTitle(
context,
R.string.label_file_size,
getFileSizeString(songFile.length())
)
try {
val audioFile = AudioFileIO.read(songFile)
val audioHeader = audioFile.audioHeader
fileFormat.text =
makeTextWithTitle(context, R.string.label_file_format, audioHeader.format)
trackLength.text = makeTextWithTitle(
context,
R.string.label_track_length,
MusicUtil.getReadableDurationString((audioHeader.trackLength * 1000).toLong())
)
bitRate.text = makeTextWithTitle(
context,
R.string.label_bit_rate,
audioHeader.bitRate + " kb/s"
)
samplingRate.text =
makeTextWithTitle(
context,
R.string.label_sampling_rate,
audioHeader.sampleRate + " Hz"
)
} catch (@NonNull e: CannotReadException) {
Log.e(TAG, "error while reading the song file", e)
// fallback
trackLength.text = makeTextWithTitle(
context,
R.string.label_track_length,
MusicUtil.getReadableDurationString(song.duration)
)
} catch (@NonNull e: IOException) {
Log.e(TAG, "error while reading the song file", e)
trackLength.text = makeTextWithTitle(
context,
R.string.label_track_length,
MusicUtil.getReadableDurationString(song.duration)
)
} catch (@NonNull e: TagException) {
Log.e(TAG, "error while reading the song file", e)
trackLength.text = makeTextWithTitle(
context,
R.string.label_track_length,
MusicUtil.getReadableDurationString(song.duration)
)
} catch (@NonNull e: ReadOnlyFileException) {
Log.e(TAG, "error while reading the song file", e)
trackLength.text = makeTextWithTitle(
context,
R.string.label_track_length,
MusicUtil.getReadableDurationString(song.duration)
)
} catch (@NonNull e: InvalidAudioFrameException) {
Log.e(TAG, "error while reading the song file", e)
trackLength.text = makeTextWithTitle(
context,
R.string.label_track_length,
MusicUtil.getReadableDurationString(song.duration)
)
}
} else {
// fallback
fileName.text = makeTextWithTitle(context, R.string.label_file_name, song.title)
trackLength.text = makeTextWithTitle(
context,
R.string.label_track_length,
MusicUtil.getReadableDurationString(song.duration)
)
}
}
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
).setTitle(R.string.action_details)
.setPositiveButton(android.R.string.ok, null)
.setView(dialogView)
.create()
}
companion object {
val TAG: String = SongDetailDialog::class.java.simpleName
fun create(song: Song): SongDetailDialog {
val dialog = SongDetailDialog()
val args = Bundle()
args.putParcelable(EXTRA_SONG, song)
dialog.arguments = args
return dialog
}
private fun makeTextWithTitle(context: Context, titleResId: Int, text: String?): Spanned {
return HtmlCompat.fromHtml(
"<b>" + context.resources.getString(titleResId) + ": " + "</b>" + text,
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
private fun getFileSizeString(sizeInBytes: Long): String {
val fileSizeInKB = sizeInBytes / 1024
val fileSizeInMB = fileSizeInKB / 1024
return "$fileSizeInMB MB"
}
}
}

View file

@ -1,104 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.ShareInstagramStory
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class SongShareDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val song: Song? = requireArguments().getParcelable(EXTRA_SONG)
val listening: String =
String.format(
getString(R.string.currently_listening_to_x_by_x),
song?.title,
song?.artistName
)
return MaterialAlertDialogBuilder(
requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert
).setTitle(R.string.what_do_you_want_to_share)
.setItems(
arrayOf(
getString(R.string.the_audio_file),
"\u201C" + listening + "\u201D",
getString(R.string.social_stories)
)
) { _, which ->
withAction(which, song, listening)
}
.create()
}
private fun withAction(
which: Int,
song: Song?,
currentlyListening: String
) {
when (which) {
0 -> {
startActivity(Intent.createChooser(song?.let {
MusicUtil.createShareSongFileIntent(
it,
requireContext()
)
}, null))
}
1 -> {
startActivity(
Intent.createChooser(
Intent()
.setAction(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_TEXT, currentlyListening)
.setType("text/plain"),
null
)
)
}
2 -> {
if (song != null) {
startActivity(
Intent(
requireContext(),
ShareInstagramStory::class.java
).putExtra(
ShareInstagramStory.EXTRA_SONG,
song
)
)
}
}
}
}
companion object {
fun create(song: Song): SongShareDialog {
val dialog = SongShareDialog()
val args = Bundle()
args.putParcelable(EXTRA_SONG, song)
dialog.arguments = args
return dialog
}
}
}

View file

@ -1,76 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.extensions
import android.app.Activity
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import com.google.android.material.appbar.MaterialToolbar
fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
toolbar.setBackgroundColor(surfaceColor())
ToolbarContentTintHelper.colorBackButton(toolbar)
setSupportActionBar(toolbar)
}
fun FragmentActivity?.addFragment(
@IdRes idRes: Int = R.id.container,
fragment: Fragment,
tag: String? = null,
addToBackStack: Boolean = false
) {
val compatActivity = this as? AppCompatActivity ?: return
compatActivity.supportFragmentManager.beginTransaction()
.apply {
add(fragment, tag)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
if (addToBackStack) {
addToBackStack(null)
}
commitNow()
}
}
fun AppCompatActivity.replaceFragment(
@IdRes id: Int = R.id.container,
fragment: Fragment,
tag: String? = null,
addToBackStack: Boolean = false
) {
val compatActivity = this ?: return
compatActivity.supportFragmentManager.beginTransaction()
.apply {
replace(id, fragment, tag)
if (addToBackStack) {
addToBackStack(null)
}
commit()
}
}
inline fun <reified T : Any> Activity.extra(key: String, default: T? = null) = lazy {
val value = intent?.extras?.get(key)
if (value is T) value else default
}
inline fun <reified T : Any> Activity.extraNotNull(key: String, default: T? = null) = lazy {
val value = intent?.extras?.get(key)
requireNotNull(if (value is T) value else default) { key }
}

View file

@ -1,80 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.extensions
import android.app.Dialog
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.widget.CheckBox
import android.widget.SeekBar
import androidx.annotation.AttrRes
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.retromusic.R
fun Int.ripAlpha(): Int {
return ColorUtil.stripAlpha(this)
}
fun Dialog.colorControlNormal() = resolveColor(android.R.attr.colorControlNormal)
fun Toolbar.backgroundTintList() {
val surfaceColor = ATHUtil.resolveColor(context, R.attr.colorSurface, Color.BLACK)
val colorStateList = ColorStateList.valueOf(surfaceColor)
backgroundTintList = colorStateList
}
fun Context.accentColor() = ThemeStore.accentColor(this)
fun Fragment.accentColor() = ThemeStore.accentColor(requireContext())
fun Context.surfaceColor() = resolveColor(R.attr.colorSurface, Color.WHITE)
fun Fragment.surfaceColor() = resolveColor(R.attr.colorSurface, Color.WHITE)
fun Context.textColorSecondary() = resolveColor(android.R.attr.textColorSecondary)
fun Fragment.textColorSecondary() = resolveColor(android.R.attr.textColorSecondary)
fun Context.colorControlNormal() = resolveColor(android.R.attr.colorControlNormal)
fun Fragment.colorControlNormal() = resolveColor(android.R.attr.colorControlNormal)
fun Context.textColorPrimary() = resolveColor(android.R.attr.textColorPrimary)
fun Fragment.textColorPrimary() = resolveColor(android.R.attr.textColorPrimary)
fun Context.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(this, attr, fallBackColor)
fun Fragment.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(requireContext(), attr, fallBackColor)
fun Dialog.resolveColor(@AttrRes attr: Int, fallBackColor: Int = 0) =
ATHUtil.resolveColor(context, attr, fallBackColor)
fun CheckBox.addAccentColor() {
buttonTintList = ColorStateList.valueOf(ThemeStore.accentColor(context))
}
fun SeekBar.addAccentColor() {
val colorState = ColorStateList.valueOf(ThemeStore.accentColor(context))
progressTintList = colorState
thumbTintList = colorState
}

View file

@ -1,62 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.extensions
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.R
fun Context.scaledDrawableResources(
@DrawableRes id: Int,
@DimenRes width: Int,
@DimenRes height: Int
): Drawable {
val w = resources.getDimension(width).toInt()
val h = resources.getDimension(height).toInt()
return scaledDrawable(id, w, h)
}
fun Context.scaledDrawable(@DrawableRes id: Int, width: Int, height: Int): Drawable {
val bmp = BitmapFactory.decodeResource(resources, id)
val bmpScaled = Bitmap.createScaledBitmap(bmp, width, height, false)
return BitmapDrawable(resources, bmpScaled)
}
fun Drawable.getBitmapDrawable(): Bitmap {
val bmp = Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
draw(canvas)
return bmp
}
fun getAdaptiveIconDrawable(context: Context): Drawable {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
AdaptiveIconDrawable(
ContextCompat.getDrawable(context, R.drawable.ic_launcher_background),
ContextCompat.getDrawable(context, R.drawable.ic_launcher_background)
)
} else {
ContextCompat.getDrawable(context, R.drawable.color_circle_gradient)!!
}
}

View file

@ -1,39 +0,0 @@
package code.name.monkey.retromusic.extensions
import android.content.Context
import android.content.res.Configuration
import android.os.PowerManager
import androidx.annotation.IntegerRes
import androidx.fragment.app.Fragment
import code.name.monkey.retromusic.util.PreferenceUtil
fun Fragment.getIntRes(@IntegerRes int: Int): Int {
return resources.getInteger(int)
}
fun Context.getIntRes(@IntegerRes int: Int): Int {
return resources.getInteger(int)
}
val Context.generalThemeValue
get() = PreferenceUtil.getGeneralThemeValue(isSystemDarkModeEnabled())
fun Context.isSystemDarkModeEnabled(): Boolean {
val isBatterySaverEnabled =
(getSystemService(Context.POWER_SERVICE) as PowerManager?)?.isPowerSaveMode ?: false
val isDarkModeEnabled =
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
return isBatterySaverEnabled or isDarkModeEnabled
}
inline fun <reified T : Any> Fragment.extra(key: String, default: T? = null) = lazy {
val value = arguments?.get(key)
if (value is T) value else default
}
inline fun <reified T : Any> Fragment.extraNotNull(key: String, default: T? = null) = lazy {
val value = arguments?.get(key)
requireNotNull(if (value is T) value else default) { key }
}

View file

@ -1,73 +0,0 @@
package code.name.monkey.retromusic.extensions
import androidx.annotation.ColorInt
import androidx.core.graphics.ColorUtils
import androidx.palette.graphics.Palette
fun getSuitableColorFor(palette: Palette, i: Int, i2: Int): Int {
val dominantSwatch = palette.dominantSwatch
if (dominantSwatch != null) {
if (hasEnoughContrast(i, dominantSwatch.rgb)) {
return dominantSwatch.rgb
}
}
val vibrantSwatch = palette.vibrantSwatch
if (vibrantSwatch != null) {
if (hasEnoughContrast(i, vibrantSwatch.rgb)) {
return vibrantSwatch.rgb
}
}
val darkVibrantSwatch = palette.darkVibrantSwatch
if (darkVibrantSwatch != null) {
if (hasEnoughContrast(i, darkVibrantSwatch.rgb)) {
return darkVibrantSwatch.rgb
}
}
val lightVibrantSwatch = palette.lightVibrantSwatch
if (lightVibrantSwatch != null) {
if (hasEnoughContrast(i, lightVibrantSwatch.rgb)) {
return lightVibrantSwatch.rgb
}
}
val darkMutedSwatch = palette.darkMutedSwatch
if (darkMutedSwatch != null) {
if (hasEnoughContrast(i, darkMutedSwatch.rgb)) {
return darkMutedSwatch.rgb
}
}
val lightMutedSwatch = palette.lightMutedSwatch
if (lightMutedSwatch != null) {
if (hasEnoughContrast(i, lightMutedSwatch.rgb)) {
return lightMutedSwatch.rgb
}
}
val mutedSwatch = palette.mutedSwatch
if (mutedSwatch != null) {
if (hasEnoughContrast(i, mutedSwatch.rgb)) {
return mutedSwatch.rgb
}
}
return i2
}
fun hasEnoughContrast(i: Int, i2: Int): Boolean {
return ColorUtils.calculateContrast(i2, i) >= 2.toDouble()
}
fun hasEnoughLuminance(i: Int): Boolean {
return ColorUtils.calculateLuminance(i) >= 0.4f.toDouble()
}
fun isBlack(fArr: FloatArray): Boolean {
return fArr[2] <= 0.035f
}
fun isNearRedLine(fArr: FloatArray): Boolean {
val f = fArr[0]
return f >= 10.0f && f <= 37.0f && fArr[1] <= 0.82f
}
fun isDark(@ColorInt i: Int): Boolean {
return ColorUtils.calculateLuminance(i) < 0.5
}

Some files were not shown because too many files have changed in this diff Show more