This commit is contained in:
Hemanth S 2022-01-05 12:11:04 +05:30
commit 3a6645ab35
116 changed files with 861 additions and 651 deletions

View file

@ -14,7 +14,7 @@ android {
vectorDrawables.useSupportLibrary = true
applicationId "code.name.monkey.retromusic"
versionCode 10556
versionCode 10557
versionName '5.6.1'
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
@ -57,6 +57,8 @@ android {
abortOnError false
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@ -86,7 +88,6 @@ static def getDate() {
new Date().format('MMddyyyyss')
}
dependencies {
implementation project(':appthemehelper')
implementation "androidx.gridlayout:gridlayout:1.0.0"
@ -149,6 +150,8 @@ dependencies {
kapt 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.12.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.github.bosphere.android-fadingedgelayout:fadingedgelayout:1.0.0'

View file

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Retro Music-Debug</string>
<bool name="md3_available">true</bool>
<bool name="allowBackup">false</bool>
</resources>

View file

@ -73,7 +73,7 @@ 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 GAP_LESS_PLAYBACK = "gap_less_playback"
const val GAP_LESS_PLAYBACK = "gapless_playback"
const val ALBUM_ART_ON_LOCK_SCREEN = "album_art_on_lock_screen"
const val BLURRED_ALBUM_ART = "blurred_album_art"
const val NEW_BLUR_AMOUNT = "new_blur_amount"
@ -135,6 +135,7 @@ const val LOCK_SCREEN = "lock_screen"
const val ALBUM_ARTISTS_ONLY = "album_artists_only"
const val ALBUM_ARTIST = "album_artist"
const val ALBUM_DETAIL_SONG_SORT_ORDER = "album_detail_song_sort_order"
const val ARTIST_DETAIL_SONG_SORT_ORDER = "artist_detail_song_sort_order"
const val LYRICS_OPTIONS = "lyrics_tab_position"
const val CHOOSE_EQUALIZER = "choose_equalizer"
const val EQUALIZER = "equalizer"
@ -153,3 +154,4 @@ const val LAST_USED_TAB = "last_used_tab"
const val WHITELIST_MUSIC = "whitelist_music"
const val MATERIAL_YOU = "material_you"
const val SNOWFALL = "snowfall"
const val LYRICS_TYPE = "lyrics_type"

View file

@ -62,7 +62,7 @@ class MainActivity : AbsCastActivity(), OnSharedPreferenceChangeListener {
if (!hasPermissions()) {
findNavController(R.id.fragment_container).navigate(R.id.permissionFragment)
}
if (BuildConfig.VERSION_CODE > PreferenceUtil.lastVersion){
if (BuildConfig.VERSION_CODE > PreferenceUtil.lastVersion && !BuildConfig.DEBUG){
NavigationUtil.gotoWhatNews(this)
}
}

View file

@ -1,144 +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 final 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.isVisible());
holder.title.setText(
holder.title.getResources().getString(categoryInfo.getCategory().getStringRes()));
holder.itemView.setOnClickListener(
v -> {
if (!(categoryInfo.isVisible() && isLastCheckedCategory(categoryInfo))) {
categoryInfo.setVisible(!categoryInfo.isVisible());
holder.checkBox.setChecked(categoryInfo.isVisible());
} 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.isVisible()) {
for (CategoryInfo c : categoryInfos) {
if (c != categoryInfo && c.isVisible()) {
return false;
}
}
}
return true;
}
static class ViewHolder extends RecyclerView.ViewHolder {
private final MaterialCheckBox checkBox;
private final View dragView;
private final 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

@ -0,0 +1,121 @@
/*
* 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.Toast
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.PreferenceDialogLibraryCategoriesListitemBinding
import code.name.monkey.retromusic.model.CategoryInfo
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.SwipeAndDragHelper
import code.name.monkey.retromusic.util.SwipeAndDragHelper.ActionCompletionContract
class CategoryInfoAdapter : RecyclerView.Adapter<CategoryInfoAdapter.ViewHolder>(),
ActionCompletionContract {
var categoryInfos: MutableList<CategoryInfo> =
PreferenceUtil.libraryCategory.toMutableList()
@SuppressLint("NotifyDataSetChanged")
set(value) {
field = value
notifyDataSetChanged()
}
private val touchHelper: ItemTouchHelper
fun attachToRecyclerView(recyclerView: RecyclerView?) {
touchHelper.attachToRecyclerView(recyclerView)
}
override fun getItemCount(): Int {
return categoryInfos.size
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val categoryInfo = categoryInfos[position]
holder.binding.checkbox.isChecked = categoryInfo.visible
holder.binding.title.text =
holder.binding.title.resources.getString(categoryInfo.category.stringRes)
holder.itemView.setOnClickListener {
if (!(categoryInfo.visible && isLastCheckedCategory(categoryInfo))) {
categoryInfo.visible = !categoryInfo.visible
holder.binding.checkbox.isChecked = categoryInfo.visible
} else {
Toast.makeText(
holder.itemView.context,
R.string.you_have_to_select_at_least_one_category,
Toast.LENGTH_SHORT
)
.show()
}
}
holder.binding.dragView.setOnTouchListener { _: View?, event: MotionEvent ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder)
}
false
}
}
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int
): ViewHolder {
return ViewHolder(
PreferenceDialogLibraryCategoriesListitemBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
}
override fun onViewMoved(oldPosition: Int, newPosition: Int) {
val categoryInfo = categoryInfos[oldPosition]
categoryInfos.removeAt(oldPosition)
categoryInfos.add(newPosition, categoryInfo)
notifyItemMoved(oldPosition, newPosition)
}
private fun isLastCheckedCategory(categoryInfo: CategoryInfo): Boolean {
if (categoryInfo.visible) {
for (c in categoryInfos) {
if (c !== categoryInfo && c.visible) {
return false
}
}
}
return true
}
class ViewHolder(val binding: PreferenceDialogLibraryCategoriesListitemBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.checkbox.buttonTintList =
ColorStateList.valueOf(accentColor(binding.checkbox.context))
}
}
init {
val swipeAndDragHelper = SwipeAndDragHelper(this)
touchHelper = ItemTouchHelper(swipeAndDragHelper)
}
}

View file

@ -14,6 +14,7 @@
*/
package code.name.monkey.retromusic.adapter
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -21,7 +22,7 @@ import android.view.ViewOutlineProvider
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.databinding.ItemGenreBinding
import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
@ -38,7 +39,6 @@ import java.util.*
class GenreAdapter(
private val activity: FragmentActivity,
var dataSet: List<Genre>,
private val mItemLayoutRes: Int,
private val listener: IGenreClickListener
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
@ -51,13 +51,13 @@ class GenreAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false))
return ViewHolder(ItemGenreBinding.inflate(LayoutInflater.from(activity), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val genre = dataSet[position]
holder.title?.text = genre.name
holder.text?.text = String.format(
holder.binding.title.text = genre.name
holder.binding.text.text = String.format(
Locale.getDefault(),
"%d %s",
genre.songCount,
@ -72,33 +72,39 @@ class GenreAdapter(
.asBitmapPalette()
.load(RetroGlideExtension.getSongModel(genreSong))
.songCoverOptions(genreSong)
.into(object : RetroMusicColoredTarget(holder.image!!) {
.into(object : RetroMusicColoredTarget(holder.binding.image) {
override fun onColorReady(colors: MediaNotificationProcessor) {
setColors(holder, colors)
}
})
// Just for a bit of shadow around image
holder.image?.outlineProvider = ViewOutlineProvider.BOUNDS
holder.binding.image.outlineProvider = ViewOutlineProvider.BOUNDS
}
private fun setColors(holder: ViewHolder, color: MediaNotificationProcessor) {
holder.imageContainerCard?.setCardBackgroundColor(color.backgroundColor)
holder.title?.setTextColor(color.primaryTextColor)
holder.text?.setTextColor(color.secondaryTextColor)
holder.binding.imageContainerCard.setCardBackgroundColor(color.backgroundColor)
holder.binding.title.setTextColor(color.primaryTextColor)
holder.binding.text.setTextColor(color.secondaryTextColor)
}
override fun getItemCount(): Int {
return dataSet.size
}
@SuppressLint("NotifyDataSetChanged")
fun swapDataSet(list: List<Genre>) {
dataSet = list
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {
inner class ViewHolder(val binding: ItemGenreBinding) : RecyclerView.ViewHolder(binding.root),
View.OnClickListener {
override fun onClick(v: View?) {
listener.onClickGenre(dataSet[layoutPosition], itemView)
}
init {
itemView.setOnClickListener(this)
}
}
}

View file

@ -248,7 +248,6 @@ class HomeAdapter(
val genreAdapter = GenreAdapter(
activity,
home.arrayList as List<Genre>,
R.layout.item_grid_genre,
this@HomeAdapter
)
recyclerView.apply {

View file

@ -1,15 +1,14 @@
package code.name.monkey.retromusic.adapter.backup
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.ItemListBackupBinding
import java.io.File
@ -21,27 +20,27 @@ class BackupAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(activity).inflate(R.layout.item_list_card, parent, false)
ItemListBackupBinding.inflate(LayoutInflater.from(activity), parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.title.text = dataSet[position].nameWithoutExtension
holder.binding.title.text = dataSet[position].nameWithoutExtension
}
override fun getItemCount(): Int = dataSet.size
@SuppressLint("NotifyDataSetChanged")
fun swapDataset(dataSet: List<File>) {
this.dataSet = ArrayList(dataSet)
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
val menu: AppCompatImageView = itemView.findViewById(R.id.menu)
inner class ViewHolder(val binding: ItemListBackupBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
menu.setOnClickListener { view ->
binding.menu.setOnClickListener { view ->
val popupMenu = PopupMenu(activity, view)
popupMenu.inflate(R.menu.menu_backup)
popupMenu.setOnMenuItemClickListener { menuItem ->

View file

@ -16,6 +16,7 @@ package code.name.monkey.retromusic.fragments
import android.animation.ValueAnimator
import android.widget.Toast
import androidx.core.animation.doOnEnd
import androidx.lifecycle.*
import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.db.*
@ -255,12 +256,14 @@ class LibraryViewModel(
}
repository.insertSongs(songEntities)
} else {
if (playlist != Playlist.empty){
val playListId = createPlaylist(PlaylistEntity(playlistName = playlist.name))
val songEntities = playlist.getSongs().map {
it.toSongEntity(playListId)
}
repository.insertSongs(songEntities)
}
}
forceReload(Playlists)
}
}
@ -378,17 +381,21 @@ class LibraryViewModel(
}
fun setFabMargin(bottomMargin: Int) {
println("Bottom Margin $bottomMargin")
val currentValue = DensityUtil.dip2px(App.getContext(), 16F) +
bottomMargin
if (currentValue != fabMargin.value) {
ValueAnimator.ofInt(fabMargin.value!!, currentValue).apply {
addUpdateListener {
fabMargin.postValue(
it.animatedValue as Int
(it.animatedValue as Int).also { bottomMarginAnimated ->
println("Bottom Margin Animated $bottomMarginAnimated")
}
)
}
start()
doOnEnd {
fabMargin.postValue(currentValue)
}
start()
}
}
}

View file

@ -5,11 +5,9 @@ import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.Spanned
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.*
import androidx.activity.addCallback
import androidx.appcompat.widget.PopupMenu
import androidx.core.os.bundleOf
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
@ -32,6 +30,7 @@ import code.name.monkey.retromusic.glide.GlideApp
import code.name.monkey.retromusic.glide.RetroGlideExtension
import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.interfaces.IAlbumClickListener
import code.name.monkey.retromusic.interfaces.ICabCallback
import code.name.monkey.retromusic.interfaces.ICabHolder
@ -39,10 +38,7 @@ import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.CustomArtistImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.*
import com.afollestad.materialcab.attached.AttachedCab
import com.afollestad.materialcab.attached.destroy
import com.afollestad.materialcab.attached.isActive
@ -71,6 +67,9 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
private var lang: String? = null
private var biography: Spanned? = null
private val savedSongSortOrder: String
get() = PreferenceUtil.artistDetailSongSortOrder
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
@ -101,7 +100,7 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
setupRecyclerView()
binding.fragmentArtistContent.playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
setOnClickListener { MusicPlayerRemote.openQueue(artist.sortedSongs, 0, true) }
}
binding.fragmentArtistContent.shuffleAction.apply {
setOnClickListener { MusicPlayerRemote.openAndShuffleQueue(artist.songs, true) }
@ -121,6 +120,7 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
requireActivity().onBackPressed()
}
}
setupSongSortButton()
binding.appBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(requireContext())
}
@ -168,7 +168,7 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
)
binding.fragmentArtistContent.songTitle.text = songText
binding.fragmentArtistContent.albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber })
songAdapter.swapDataSet(artist.sortedSongs)
albumAdapter.swapDataSet(artist.albums)
}
@ -289,6 +289,53 @@ abstract class AbsArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragm
return true
}
private fun setupSongSortButton() {
binding.fragmentArtistContent.songSortOrder.setOnClickListener {
PopupMenu(requireContext(), binding.fragmentArtistContent.songSortOrder).apply {
inflate(R.menu.menu_artist_song_sort_order)
setUpSortOrderMenu(menu)
setOnMenuItemClickListener { menuItem ->
val sortOrder = when (menuItem.itemId) {
R.id.action_sort_order_title -> SortOrder.ArtistSongSortOrder.SONG_A_Z
R.id.action_sort_order_title_desc -> SortOrder.ArtistSongSortOrder.SONG_Z_A
R.id.action_sort_order_album -> SortOrder.ArtistSongSortOrder.SONG_ALBUM
R.id.action_sort_order_year -> SortOrder.ArtistSongSortOrder.SONG_YEAR
R.id.action_sort_order_song_duration -> SortOrder.ArtistSongSortOrder.SONG_DURATION
else -> {
throw IllegalArgumentException("invalid ${menuItem.title}")
}
}
menuItem.isChecked = true
setSaveSortOrder(sortOrder)
return@setOnMenuItemClickListener true
}
show()
}
}
}
private fun setSaveSortOrder(sortOrder: String) {
PreferenceUtil.artistDetailSongSortOrder = sortOrder
songAdapter.swapDataSet(artist.sortedSongs)
}
private fun setUpSortOrderMenu(sortOrder: Menu) {
when (savedSongSortOrder) {
SortOrder.ArtistSongSortOrder.SONG_A_Z -> sortOrder.findItem(R.id.action_sort_order_title).isChecked = true
SortOrder.ArtistSongSortOrder.SONG_Z_A -> sortOrder.findItem(R.id.action_sort_order_title_desc).isChecked = true
SortOrder.ArtistSongSortOrder.SONG_ALBUM ->
sortOrder.findItem(R.id.action_sort_order_album).isChecked = true
SortOrder.ArtistSongSortOrder.SONG_YEAR ->
sortOrder.findItem(R.id.action_sort_order_year).isChecked = true
SortOrder.ArtistSongSortOrder.SONG_DURATION ->
sortOrder.findItem(R.id.action_sort_order_song_duration).isChecked = true
else-> {
throw IllegalArgumentException("invalid $savedSongSortOrder")
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {

View file

@ -17,6 +17,8 @@ import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.backup.BackupAdapter
import code.name.monkey.retromusic.databinding.FragmentBackupBinding
import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.accentOutlineColor
import code.name.monkey.retromusic.helper.BackupHelper
import code.name.monkey.retromusic.helper.sanitize
import code.name.monkey.retromusic.util.BackupUtil
@ -45,7 +47,7 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
else
backupAdapter?.swapDataset(listOf())
}
backupViewModel.loadBackups()
backupViewModel.loadBackups(requireContext())
val openFilePicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
lifecycleScope.launch(Dispatchers.IO) {
it?.let {
@ -55,6 +57,8 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
}
}
}
binding.createBackup.accentOutlineColor()
binding.restoreBackup.accentColor()
binding.createBackup.setOnClickListener {
showCreateBackupDialog()
}
@ -91,11 +95,11 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
MaterialDialog(requireContext()).show {
cornerRadius(res = R.dimen.m3_card_corner_radius)
title(res = R.string.action_rename)
input(prefill = System.currentTimeMillis().toString()) { _, text ->
input(prefill = BackupHelper.getTimeStamp()) { _, text ->
// Text submitted with the action button
lifecycleScope.launch {
BackupHelper.createBackup(requireContext(), text.sanitize())
backupViewModel.loadBackups()
backupViewModel.loadBackups(requireContext())
}
}
positiveButton(android.R.string.ok)
@ -125,7 +129,7 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
Toast.LENGTH_SHORT
).show()
}
backupViewModel.loadBackups()
backupViewModel.loadBackups(requireContext())
return true
}
R.id.action_share -> {
@ -143,10 +147,10 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
input(prefill = file.nameWithoutExtension) { _, text ->
// Text submitted with the action button
val renamedFile =
File(file.parent + File.separator + text + BackupHelper.APPEND_EXTENSION)
File(file.parent, "$text${BackupHelper.APPEND_EXTENSION}")
if (!renamedFile.exists()) {
file.renameTo(renamedFile)
backupViewModel.loadBackups()
backupViewModel.loadBackups(requireContext())
} else {
Toast.makeText(
requireContext(),

View file

@ -1,6 +1,7 @@
package code.name.monkey.retromusic.fragments.backup
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@ -19,8 +20,8 @@ class BackupViewModel : ViewModel() {
private val backupsMutableLiveData = MutableLiveData<List<File>>()
val backupsLiveData: LiveData<List<File>> = backupsMutableLiveData
fun loadBackups() {
File(BackupHelper.backupRootPath).listFiles { _, name ->
fun loadBackups(context: Context) {
BackupHelper.getBackupRoot(context).listFiles { _, name ->
return@listFiles name.endsWith(BackupHelper.BACKUP_EXTENSION)
}?.toList()?.let {
backupsMutableLiveData.value = it
@ -29,6 +30,8 @@ class BackupViewModel : ViewModel() {
suspend fun restoreBackup(activity: Activity, inputStream: InputStream?, contents: List<BackupContent>) {
BackupHelper.restoreBackup(activity, inputStream, contents)
if (contents.contains(BackupContent.SETTINGS) or contents.contains(BackupContent.CUSTOM_ARTIST_IMAGES)) {
// We have to restart App when Preferences i.e. Settings or Artist Images are to be restored
withContext(Dispatchers.Main) {
val intent = Intent(
activity,
@ -39,3 +42,4 @@ class BackupViewModel : ViewModel() {
}
}
}
}

View file

@ -3,9 +3,11 @@ package code.name.monkey.retromusic.fragments.backup
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.databinding.ActivityRestoreBinding
import code.name.monkey.retromusic.helper.BackupContent
@ -27,6 +29,7 @@ class RestoreActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
binding = ActivityRestoreBinding.inflate(layoutInflater)
setContentView(binding.root)
setWidth()
val backupUri = intent?.data
binding.backupName.setText(getFileName(backupUri))
binding.cancelButton.setOnClickListener {
@ -35,7 +38,6 @@ class RestoreActivity : AppCompatActivity() {
binding.restoreButton.setOnClickListener {
val backupContents = mutableListOf<BackupContent>()
if (binding.checkSettings.isChecked) backupContents.add(SETTINGS)
if (binding.checkQueue.isChecked) backupContents.add(QUEUE)
if (binding.checkDatabases.isChecked) backupContents.add(PLAYLISTS)
if (binding.checkArtistImages.isChecked) backupContents.add(CUSTOM_ARTIST_IMAGES)
if (binding.checkUserImages.isChecked) backupContents.add(USER_IMAGES)
@ -67,17 +69,13 @@ class RestoreActivity : AppCompatActivity() {
return uri.lastPathSegment
}
"content" -> {
val proj = arrayOf(MediaStore.Images.Media.TITLE)
val proj = arrayOf(MediaStore.Files.FileColumns.DISPLAY_NAME)
contentResolver.query(
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
} else {
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}, proj, null, null, null
uri, proj, null, null, null
)?.use { cursor ->
if (cursor.count != 0) {
val columnIndex: Int =
cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE)
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
cursor.moveToFirst()
return cursor.getString(columnIndex)
}
@ -86,4 +84,9 @@ class RestoreActivity : AppCompatActivity() {
}
return "Backup"
}
private fun setWidth() {
val width = resources.displayMetrics.widthPixels * 0.8
binding.root.updateLayoutParams<ViewGroup.LayoutParams> { this.width = width.toInt() }
}
}

View file

@ -39,6 +39,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.navOptions
import androidx.viewpager.widget.ViewPager
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.R
@ -237,7 +238,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
val isFavorite: Boolean =
libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Main) {
val icon = if (animate) {
val icon = if (animate && VersionUtils.hasMarshmallow()) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border

View file

@ -164,7 +164,7 @@ abstract class AbsRecyclerViewFragment<A : RecyclerView.Adapter<*>, LM : Recycle
if (itemCount > 0 && MusicPlayerRemote.playingQueue.isNotEmpty()) {
binding.recyclerView.updatePadding(bottom = dip(R.dimen.mini_player_height_expanded))
} else {
binding.recyclerView.updatePadding(bottom = dip(R.dimen.mini_player_height))
binding.recyclerView.updatePadding(bottom = dip(R.dimen.bottom_nav_height))
}
}

View file

@ -62,7 +62,7 @@ GenresFragment : AbsRecyclerViewFragment<GenreAdapter, LinearLayoutManager>(),
override fun createAdapter(): GenreAdapter {
val dataSet = if (adapter == null) ArrayList() else adapter!!.dataSet
return GenreAdapter(requireActivity(), dataSet, R.layout.item_genre, this)
return GenreAdapter(requireActivity(), dataSet, this)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View file

@ -29,6 +29,7 @@ import android.widget.Toast
import androidx.core.view.doOnPreDraw
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import code.name.monkey.retromusic.Constants.USER_BANNER
import code.name.monkey.retromusic.Constants.USER_PROFILE
@ -50,7 +51,6 @@ import com.bumptech.glide.request.target.Target
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.constant.ImageProvider
import com.google.android.material.transition.MaterialContainerTransform
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -202,15 +202,15 @@ class UserInfoFragment : Fragment() {
}
private fun saveImage(bitmap: Bitmap, fileName: String) {
CoroutineScope(Dispatchers.IO).launch {
lifecycleScope.launch(Dispatchers.IO) {
val appDir = requireContext().filesDir
val file = File(appDir, fileName)
var successful = false
runCatching {
val os = BufferedOutputStream(FileOutputStream(file))
BufferedOutputStream(FileOutputStream(file)).use {
successful = ImageUtil.resizeBitmap(bitmap, 2048)
.compress(Bitmap.CompressFormat.WEBP, 100, os)
withContext(Dispatchers.IO) { os.close() }
.compress(Bitmap.CompressFormat.WEBP, 100, it)
}
}.onFailure {
it.printStackTrace()
}

View file

@ -14,17 +14,19 @@
*/
package code.name.monkey.retromusic.fragments.player
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.core.view.isInvisible
import androidx.core.animation.doOnEnd
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.viewpager.widget.ViewPager
import code.name.monkey.appthemehelper.util.MaterialValueHelper
import code.name.monkey.retromusic.LYRICS_TYPE
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.SHOW_LYRICS
import code.name.monkey.retromusic.adapter.album.AlbumCoverPagerAdapter
@ -34,7 +36,6 @@ import code.name.monkey.retromusic.extensions.isColorLight
import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.fragments.base.AbsPlayerFragment
import code.name.monkey.retromusic.fragments.base.goToLyrics
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
@ -43,6 +44,7 @@ import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.transform.CarousalPagerTransformer
import code.name.monkey.retromusic.transform.ParallaxPagerTransformer
import code.name.monkey.retromusic.util.LyricUtil
import code.name.monkey.retromusic.util.LyricsType
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import kotlinx.coroutines.Dispatchers
@ -201,6 +203,8 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
showLyrics(false)
progressViewUpdateHelper?.stop()
}
} else if (key == LYRICS_TYPE) {
maybeInitLyrics()
}
}
@ -223,8 +227,21 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
}
private fun showLyrics(visible: Boolean) {
lrcView.isVisible = visible
viewPager.isInvisible = visible
binding.coverLyrics.isVisible = false
binding.lyricsView.isVisible = false
binding.viewPager.isVisible = true
val lyrics: View = if (PreferenceUtil.lyricsType == LyricsType.REPLACE_LYRICS) {
ObjectAnimator.ofFloat(viewPager, View.ALPHA, if (visible) 0F else 1F).start()
lrcView
} else {
binding.coverLyrics
}
ObjectAnimator.ofFloat(lyrics, View.ALPHA, if (visible) 1F else 0F).apply {
doOnEnd {
lyrics.isVisible = visible
}
start()
}
}
private fun maybeInitLyrics() {
@ -232,9 +249,9 @@ class PlayerAlbumCoverFragment : AbsMusicServiceFragment(R.layout.fragment_playe
// Don't show lyrics container for below conditions
if (lyricViewNpsList.contains(nps) && PreferenceUtil.showLyrics) {
showLyrics(true)
if (PreferenceUtil.lyricsType == LyricsType.REPLACE_LYRICS) {
progressViewUpdateHelper?.start()
lrcView.animate().alpha(1f).duration =
AbsPlayerFragment.VISIBILITY_ANIM_DURATION
}
} else {
showLyrics(false)
progressViewUpdateHelper?.stop()

View file

@ -16,6 +16,7 @@ package code.name.monkey.retromusic.fragments.player.color
import android.animation.ValueAnimator
import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.core.animation.doOnEnd
@ -60,12 +61,14 @@ class ColorFragment : AbsPlayerFragment(R.layout.fragment_color_player) {
_binding?.root?.setBackgroundColor(color.backgroundColor)
}
animator.start()
Handler().post {
ToolbarContentTintHelper.colorizeToolbar(
binding.playerToolbar,
color.secondaryTextColor,
requireActivity()
)
}
}
override fun onFavoriteToggled() {
toggleFavorite(MusicPlayerRemote.currentSong)

View file

@ -30,6 +30,7 @@ import android.widget.SeekBar
import androidx.appcompat.widget.PopupMenu
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.databinding.FragmentFullPlayerControlsBinding
import code.name.monkey.retromusic.db.PlaylistEntity
@ -320,7 +321,7 @@ class FullPlaybackControlsFragment :
val isFavorite: Boolean =
libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Dispatchers.Main) {
val icon = if (animate) {
val icon = if (animate && VersionUtils.hasMarshmallow()) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border

View file

@ -35,6 +35,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import code.name.monkey.appthemehelper.util.ColorUtil
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.RetroBottomSheetBehavior
import code.name.monkey.retromusic.adapter.song.PlayingQueueAdapter
@ -89,8 +90,8 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
binding.playerQueueSheet.updatePadding(
top = (slideOffset * binding.statusBarLayout.statusBar.height).toInt()
)
binding.recyclerView.updatePadding(
top = ((1 - slideOffset) * navBarHeight).toInt()
binding.container.updatePadding(
bottom = ((1 - slideOffset) * navBarHeight).toInt()
)
}
@ -158,9 +159,9 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
}
ViewCompat.setOnApplyWindowInsetsListener(
(binding.container)
) { _: View, insets: WindowInsetsCompat ->
) { v: View, insets: WindowInsetsCompat ->
navBarHeight = insets.safeGetBottomInsets()
binding.recyclerView.updatePadding(top = navBarHeight)
v.updatePadding(bottom = navBarHeight)
insets
}
binding.playbackControlsFragment.root.drawAboveSystemBars()
@ -282,7 +283,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
val isFavorite: Boolean =
libraryViewModel.isSongFavorite(MusicPlayerRemote.currentSong.id)
withContext(Dispatchers.Main) {
val icon = if (animate) {
val icon = if (animate && VersionUtils.hasMarshmallow()) {
if (isFavorite) R.drawable.avd_favorite else R.drawable.avd_unfavorite
} else {
if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
@ -453,7 +454,7 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
private fun updateLabel() {
(MusicPlayerRemote.playingQueue.size - 1).apply {
if (this == (MusicPlayerRemote.position)) {
binding.nextSong.text = "Last song"
binding.nextSong.text = context?.resources?.getString(R.string.last_song)
} else {
val title = MusicPlayerRemote.playingQueue[MusicPlayerRemote.position + 1].title
binding.nextSong.text = title
@ -473,8 +474,12 @@ class GradientPlayerFragment : AbsPlayerFragment(R.layout.fragment_gradient_play
oldBottom: Int
) {
val panel = getQueuePanel()
if (panel.state == STATE_COLLAPSED) {
panel.peekHeight = binding.container.height
} else if (panel.state == STATE_EXPANDED) {
panel.peekHeight = binding.container.height + navBarHeight
}
}
private fun setupRecyclerView() {
playingQueueAdapter = PlayingQueueAdapter(

View file

@ -2,39 +2,49 @@ package code.name.monkey.retromusic.helper
import android.content.Context
import android.os.Environment
import android.util.Log
import android.widget.Toast
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongEntity
import code.name.monkey.retromusic.helper.BackupContent.*
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.Repository
import code.name.monkey.retromusic.repository.SongRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
object BackupHelper {
object BackupHelper : KoinComponent {
private val repository by inject<Repository>()
private val songRepository by inject<SongRepository>()
suspend fun createBackup(context: Context, name: String) {
val backupFile =
File(backupRootPath + File.separator + name + APPEND_EXTENSION)
File(getBackupRoot(context), name + APPEND_EXTENSION)
if (backupFile.parentFile?.exists() != true) {
backupFile.parentFile?.mkdirs()
}
val zipItems = mutableListOf<ZipItem>()
zipItems.addAll(getDatabaseZipItems(context))
zipItems.addAll(getPlaylistZipItems(context))
zipItems.addAll(getSettingsZipItems(context))
getUserImageZipItems(context)?.let { zipItems.addAll(it) }
zipItems.addAll(getCustomArtistZipItems(context))
zipItems.addAll(getQueueZipItems(context))
zipAll(zipItems, backupFile)
// Clean Cache Playlist Directory
File(context.filesDir, PLAYLISTS_PATH).deleteRecursively()
}
private suspend fun zipAll(zipItems: List<ZipItem>, backupFile: File) =
withContext(Dispatchers.IO) {
kotlin.runCatching {
runCatching {
ZipOutputStream(BufferedOutputStream(FileOutputStream(backupFile))).use { out ->
for (zipItem in zipItems) {
FileInputStream(zipItem.filePath).use { fi ->
@ -51,7 +61,6 @@ object BackupHelper {
Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT)
.show()
}
throw Exception(it)
}.onSuccess {
withContext(Dispatchers.Main) {
Toast.makeText(
@ -62,34 +71,39 @@ object BackupHelper {
.show()
}
}
}
private fun getDatabaseZipItems(context: Context): List<ZipItem> {
return context.databaseList().filter {
it.endsWith(".db") && it != queueDatabase
}.map {
ZipItem(context.getDatabasePath(it).absolutePath, "$DATABASES_PATH${File.separator}$it")
private suspend fun getPlaylistZipItems(context: Context): List<ZipItem> {
val playlistZipItems = mutableListOf<ZipItem>()
// Cache Playlist files in App storage
val playlistFolder = File(context.filesDir, PLAYLISTS_PATH)
if (!playlistFolder.exists()) {
playlistFolder.mkdirs()
}
}
private fun getQueueZipItems(context: Context): List<ZipItem> {
Log.d("RetroMusic", context.getDatabasePath(queueDatabase).absolutePath)
return listOf(
for (playlist in repository.fetchPlaylistWithSongs()) {
runCatching {
M3UWriter.writeIO(playlistFolder, playlist)
}.onSuccess { playlistFile ->
if (playlistFile.exists()) {
playlistZipItems.add(
ZipItem(
context.getDatabasePath(queueDatabase).absolutePath,
"$QUEUE_PATH${File.separator}$queueDatabase"
playlistFile.absolutePath,
PLAYLISTS_PATH.child(playlistFile.name)
)
)
}
}
}
return playlistZipItems
}
private fun getSettingsZipItems(context: Context): List<ZipItem> {
val sharedPrefPath = context.filesDir.parentFile?.absolutePath + "/shared_prefs/"
val sharedPrefPath = File(context.filesDir.parentFile, "shared_prefs")
return listOf(
"${BuildConfig.APPLICATION_ID}_preferences.xml", // App settings pref path
"$THEME_PREFS_KEY_DEFAULT.xml" // appthemehelper pref path
).map {
ZipItem(sharedPrefPath + it, "$SETTINGS_PATH${File.separator}$it")
ZipItem(File(sharedPrefPath, it).absolutePath, SETTINGS_PATH.child(it))
}
}
@ -97,35 +111,33 @@ object BackupHelper {
return context.filesDir.listFiles { _, name ->
name.endsWith(".jpg")
}?.map {
ZipItem(it.absolutePath, "$IMAGES_PATH${File.separator}${it.name}")
ZipItem(it.absolutePath, IMAGES_PATH.child(it.name))
}
}
private fun getCustomArtistZipItems(context: Context): List<ZipItem> {
val zipItemList = mutableListOf<ZipItem>()
val sharedPrefPath = context.filesDir.parentFile?.absolutePath + "/shared_prefs/"
val sharedPrefPath = File(context.filesDir.parentFile, "shared_prefs")
zipItemList.addAll(
File(context.filesDir, "custom_artist_images")
.listFiles()?.map {
ZipItem(
it.absolutePath,
"$CUSTOM_ARTISTS_PATH${File.separator}custom_artist_images${File.separator}${it.name}"
CUSTOM_ARTISTS_PATH.child("custom_artist_images").child(it.name)
)
}?.toList() ?: listOf()
)
File(sharedPrefPath + File.separator + "custom_artist_image.xml").let {
File(sharedPrefPath, "custom_artist_image.xml").let {
if (it.exists()) {
zipItemList.add(
ZipItem(
it.absolutePath,
"$CUSTOM_ARTISTS_PATH${File.separator}prefs${File.separator}custom_artist_image.xml"
CUSTOM_ARTISTS_PATH.child("prefs").child("custom_artist_image.xml")
)
)
}
}
return zipItemList
}
@ -138,23 +150,19 @@ object BackupHelper {
ZipInputStream(inputStream).use {
var entry = it.nextEntry
while (entry != null) {
if (entry.isDatabaseEntry() && contents.contains(PLAYLISTS)) {
restoreDatabase(context, it, entry)
if (entry.isPlaylistEntry() && contents.contains(PLAYLISTS)) {
restorePlaylists(it, entry)
} else if (entry.isPreferenceEntry() && contents.contains(SETTINGS)) {
restorePreferences(context, it, entry)
} else if (entry.isImageEntry() && contents.contains(USER_IMAGES)) {
restoreImages(context, it, entry)
} else if (entry.isCustomArtistImageEntry() && contents.contains(
CUSTOM_ARTIST_IMAGES
)
) {
restoreCustomArtistImages(context, it, entry)
} else if (entry.isCustomArtistEntry() && contents.contains(CUSTOM_ARTIST_IMAGES)) {
if (entry.isCustomArtistPrefEntry()) {
restoreCustomArtistPrefs(context, it, entry)
} else if (entry.isQueueEntry() && contents.contains(QUEUE)) {
restoreQueueDatabase(context, it, entry)
} else if (entry.isCustomArtistImageEntry()) {
restoreCustomArtistImages(context, it, entry)
}
}
entry = it.nextEntry
}
}
@ -165,14 +173,11 @@ object BackupHelper {
}
private fun restoreImages(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
val filePath =
context.filesDir.path + File.separator + zipEntry.getFileName()
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
val file = File(
context.filesDir.path, zipEntry.getFileName()
)
BufferedOutputStream(FileOutputStream(file)).use { bos ->
zipIn.copyTo(bos)
}
}
@ -184,39 +189,38 @@ object BackupHelper {
file.delete()
}
BufferedOutputStream(FileOutputStream(file)).use { bos ->
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
zipIn.copyTo(bos)
}
}
private fun restoreDatabase(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
val filePath =
context.filesDir.parent!! + File.separator + DATABASES_PATH + File.separator + zipEntry.getFileName()
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
}
}
private suspend fun restorePlaylists(
zipIn: ZipInputStream,
zipEntry: ZipEntry
) {
val playlistName = zipEntry.getFileName().substringBeforeLast(".")
val songs = mutableListOf<Song>()
private fun restoreQueueDatabase(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true) {
putInt("POSITION", 0)
// Get songs from m3u playlist files
zipIn.bufferedReader().lineSequence().forEach { line ->
if (line.startsWith(File.separator)) {
if (File(line).exists()) {
songs.addAll(songRepository.songsByFilePath(line))
}
val filePath =
context.filesDir.parent!! + File.separator + DATABASES_PATH + File.separator + zipEntry.getFileName()
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
}
val playlistEntity = repository.checkPlaylistExists(playlistName).firstOrNull()
if (playlistEntity != null) {
val songEntities = songs.map {
it.toSongEntity(playlistEntity.playListId)
}
repository.insertSongs(songEntities)
} else {
val playListId = repository.createPlaylist(PlaylistEntity(playlistName = playlistName))
val songEntities = songs.map {
it.toSongEntity(playListId)
}
repository.insertSongs(songEntities)
}
}
private fun restoreCustomArtistImages(
@ -237,11 +241,7 @@ object BackupHelper {
)
)
).use { bos ->
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
zipIn.copyTo(bos)
}
}
@ -250,32 +250,30 @@ object BackupHelper {
zipIn: ZipInputStream,
zipEntry: ZipEntry
) {
val filePath =
context.filesDir.parentFile?.absolutePath + "/shared_prefs/" + zipEntry.getFileName()
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
var read: Int
while (zipIn.read(bytesIn).also { read = it } != -1) {
bos.write(bytesIn, 0, read)
}
val file =
File(context.filesDir.parentFile, "shared_prefs".child(zipEntry.getFileName()))
BufferedOutputStream(FileOutputStream(file)).use { bos ->
zipIn.copyTo(bos)
}
}
val backupRootPath =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
.toString() + "/RetroMusic/Backups/"
fun getBackupRoot(context: Context): File {
return File(
context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS),
"RetroMusic/Backups"
)
}
const val BACKUP_EXTENSION = "rmbak"
const val APPEND_EXTENSION = ".$BACKUP_EXTENSION"
private const val DATABASES_PATH = "databases"
private const val QUEUE_PATH = "queue"
private const val PLAYLISTS_PATH = "Playlists"
private const val SETTINGS_PATH = "prefs"
private const val IMAGES_PATH = "userImages"
private const val CUSTOM_ARTISTS_PATH = "artistImages"
private const val THEME_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"
private const val queueDatabase = "music_playback_state.db"
private fun ZipEntry.isDatabaseEntry(): Boolean {
return name.startsWith(DATABASES_PATH)
private fun ZipEntry.isPlaylistEntry(): Boolean {
return name.startsWith(PLAYLISTS_PATH)
}
private fun ZipEntry.isPreferenceEntry(): Boolean {
@ -286,6 +284,10 @@ object BackupHelper {
return name.startsWith(IMAGES_PATH)
}
private fun ZipEntry.isCustomArtistEntry(): Boolean {
return name.startsWith(CUSTOM_ARTISTS_PATH)
}
private fun ZipEntry.isCustomArtistImageEntry(): Boolean {
return name.startsWith(CUSTOM_ARTISTS_PATH) && name.contains("custom_artist_images")
}
@ -294,12 +296,12 @@ object BackupHelper {
return name.startsWith(CUSTOM_ARTISTS_PATH) && name.contains("prefs")
}
private fun ZipEntry.isQueueEntry(): Boolean {
return name.startsWith(QUEUE_PATH)
private fun ZipEntry.getFileName(): String {
return name.substring(name.lastIndexOf(File.separator) + 1)
}
private fun ZipEntry.getFileName(): String {
return name.substring(name.lastIndexOf(File.separator))
fun getTimeStamp(): String {
return SimpleDateFormat("dd-MMM yyyy HHmmss", Locale.getDefault()).format(Date())
}
}
@ -318,10 +320,13 @@ fun CharSequence.sanitize(): String {
.replace("&", "_")
}
fun String.child(child: String): String {
return this + File.separator + child
}
enum class BackupContent {
SETTINGS,
USER_IMAGES,
CUSTOM_ARTIST_IMAGES,
PLAYLISTS,
QUEUE
PLAYLISTS
}

View file

@ -459,7 +459,7 @@ object MusicPlayerRemote : KoinComponent {
songFile = File(path)
}
if (songFile == null && uri.path != null) {
songFile = File(uri.path)
songFile = File(uri.path!!)
}
if (songFile != null) {
songs = songRepository.songsByFilePath(songFile.absolutePath)

View file

@ -134,7 +134,7 @@ class SortOrder {
companion object {
/* Artist song sort order A-Z */
private const val SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER
const val SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER
/* Artist song sort order Z-A */
const val SONG_Z_A = "$SONG_A_Z DESC"

View file

@ -167,13 +167,13 @@ class CoverLrcView @JvmOverloads constructor(
isShowTimeline = false
removeCallbacks(hideTimelineRunnable)
mCurrentLine = centerLine
invalidate()
animateCurrentTextSize()
return true
}
} else {
callOnClick()
return true
}
}
return super.onSingleTapConfirmed(e)
}
}
@ -458,6 +458,7 @@ class CoverLrcView @JvmOverloads constructor(
mCurrentLine = line
if (!isShowTimeline) {
smoothScrollTo(line)
animateCurrentTextSize()
} else {
invalidate()
}
@ -536,6 +537,18 @@ class CoverLrcView @JvmOverloads constructor(
canvas.restore()
}
fun animateCurrentTextSize() {
val currentTextSize = mCurrentTextSize
ValueAnimator.ofFloat(mNormalTextSize, currentTextSize).apply {
addUpdateListener {
mCurrentTextSize = it.animatedValue as Float
invalidate()
}
duration = 300L
start()
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP
@ -613,7 +626,7 @@ class CoverLrcView @JvmOverloads constructor(
private fun adjustCenter() {
smoothScrollTo(centerLine, ADJUST_DURATION)
}
/** 滚动到某一行 */
/** 滚动到某一行 */
private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) {
val offset = getOffset(line)

View file

@ -14,7 +14,9 @@
package code.name.monkey.retromusic.model
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
data class Artist(
val id: Long,
@ -29,7 +31,7 @@ data class Artist(
name = artistName
}
var name: String = ""
var name: String = "-"
get() {
val name = if (isAlbumArtist) getAlbumArtistName()
else getArtistName()
@ -57,6 +59,39 @@ data class Artist(
val songs: List<Song>
get() = albums.flatMap { it.songs }
val sortedSongs: List<Song>
get() = songs.sortedWith(
when (PreferenceUtil.artistDetailSongSortOrder) {
SortOrder.ArtistSongSortOrder.SONG_A_Z -> { o1, o2 ->
o1.title.compareTo(
o2.title
)
}
SortOrder.ArtistSongSortOrder.SONG_Z_A -> { o1, o2 ->
o2.title.compareTo(
o1.title
)
}
SortOrder.ArtistSongSortOrder.SONG_ALBUM -> { o1, o2 ->
o1.albumName.compareTo(
o2.albumName
)
}
SortOrder.ArtistSongSortOrder.SONG_YEAR -> { o1, o2 ->
o2.year.compareTo(
o1.year
)
}
SortOrder.ArtistSongSortOrder.SONG_DURATION -> { o1, o2 ->
o1.duration.compareTo(
o2.duration
)
}
else -> {
throw IllegalArgumentException("invalid ${PreferenceUtil.artistDetailSongSortOrder}")
}
})
fun safeGetFirstAlbum(): Album {
return albums.firstOrNull() ?: Album.empty
}

View file

@ -55,7 +55,6 @@ class LibraryPreferenceDialog : DialogFragment() {
.inflate(R.layout.preference_dialog_library_categories, null)
val categoryAdapter = CategoryInfoAdapter()
categoryAdapter.categoryInfos = PreferenceUtil.libraryCategory
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(activity)
recyclerView.adapter = categoryAdapter

View file

@ -31,7 +31,9 @@ import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.service.playback.Playback;
import code.name.monkey.retromusic.util.PreferenceUtil;
/** @author Andrew Neal, Karim Abou Zeid (kabouzeid) */
/**
* @author Andrew Neal, Karim Abou Zeid (kabouzeid)
*/
public class MultiPlayer
implements Playback, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
public static final String TAG = MultiPlayer.class.getSimpleName();
@ -45,7 +47,9 @@ public class MultiPlayer
private boolean mIsInitialized = false;
/** Constructor of <code>MultiPlayer</code> */
/**
* Constructor of <code>MultiPlayer</code>
*/
MultiPlayer(final Context context) {
this.context = context;
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
@ -155,13 +159,17 @@ public class MultiPlayer
this.callbacks = callbacks;
}
/** @return True if the player is ready to go, false otherwise */
/**
* @return True if the player is ready to go, false otherwise
*/
@Override
public boolean isInitialized() {
return mIsInitialized;
}
/** Starts or resumes playback. */
/**
* Starts or resumes playback.
*/
@Override
public boolean start() {
try {
@ -172,14 +180,18 @@ public class MultiPlayer
}
}
/** Resets the MediaPlayer to its uninitialized state. */
/**
* Resets the MediaPlayer to its uninitialized state.
*/
@Override
public void stop() {
mCurrentMediaPlayer.reset();
mIsInitialized = false;
}
/** Releases resources associated with this MediaPlayer object. */
/**
* Releases resources associated with this MediaPlayer object.
*/
@Override
public void release() {
stop();
@ -189,7 +201,9 @@ public class MultiPlayer
}
}
/** Pauses playback. Call start() to resume. */
/**
* Pauses playback. Call start() to resume.
*/
@Override
public boolean pause() {
try {
@ -200,7 +214,9 @@ public class MultiPlayer
}
}
/** Checks whether the MultiPlayer is playing. */
/**
* Checks whether the MultiPlayer is playing.
*/
@Override
public boolean isPlaying() {
return mIsInitialized && mCurrentMediaPlayer.isPlaying();
@ -291,7 +307,9 @@ public class MultiPlayer
return mCurrentMediaPlayer.getAudioSessionId();
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
mIsInitialized = false;
@ -308,7 +326,9 @@ public class MultiPlayer
return false;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public void onCompletion(final MediaPlayer mp) {
if (mp.equals(mCurrentMediaPlayer) && mNextMediaPlayer != null) {
@ -324,5 +344,6 @@ public class MultiPlayer
}
@Override
public void setCrossFadeDuration(int duration) { }
public void setCrossFadeDuration(int duration) {
}
}

View file

@ -431,9 +431,7 @@ public class MusicService extends MediaBrowserServiceCompat
registerReceiver(lockScreenReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
setSessionToken(mediaSession.getSessionToken());
if (VersionUtils.INSTANCE.hasMarshmallow()) {
notificationManager = getSystemService(NotificationManager.class);
}
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
initNotification();
mediaStoreObserver = new MediaStoreObserver(this, playerHandler);

View file

@ -130,6 +130,13 @@ object PreferenceUtil {
)
set(value) = sharedPreferences.edit { putString(ALBUM_DETAIL_SONG_SORT_ORDER, value) }
var artistDetailSongSortOrder
get() = sharedPreferences.getStringOrDefault(
ARTIST_DETAIL_SONG_SORT_ORDER,
ArtistSongSortOrder.SONG_A_Z
)
set(value) = sharedPreferences.edit { putString(ARTIST_DETAIL_SONG_SORT_ORDER, value) }
var songSortOrder
get() = sharedPreferences.getStringOrDefault(
SONG_SORT_ORDER,
@ -660,4 +667,14 @@ object PreferenceUtil {
val isSnowFalling
get() = sharedPreferences.getBoolean(SNOWFALL, false)
val lyricsType: LyricsType
get() = if (sharedPreferences.getString(LYRICS_TYPE, "0") == "0") {
LyricsType.REPLACE_LYRICS
} else {
LyricsType.OVER_LYRICS
}
}
enum class LyricsType {
REPLACE_LYRICS, OVER_LYRICS
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -181,6 +181,7 @@
android:layout_height="wrap_content"
android:layout_weight="8"
android:maxHeight="3dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
android:progressTint="@color/md_white_1000"
android:splitTrack="false"

View file

@ -181,6 +181,7 @@
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingVertical="@dimen/seekbar_padding"
app:layout_constraintBottom_toTopOf="@id/songInfo"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -5,7 +5,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">
android:focusable="true"
android:background="?attr/colorSurface">
<View
android:id="@+id/colorGradientBackground"

View file

@ -188,6 +188,7 @@
android:layout_height="wrap_content"
android:layout_weight="8"
android:maxHeight="3dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
android:progressTint="@color/md_white_1000"
android:splitTrack="false"

View file

@ -38,14 +38,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/databases_description" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_queue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/now_playing_queue" />
android:text="@string/playlists" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/check_user_images"

View file

@ -19,6 +19,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="3dp"
android:paddingVertical="@dimen/seekbar_padding"
android:splitTrack="false"
tools:progress="20" />

View file

@ -27,6 +27,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<code.name.monkey.retromusic.views.insets.InsetsConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@ -79,10 +79,23 @@
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/song_sort_order"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/albumRecyclerView"/>
<ImageButton
android:id="@+id/song_sort_order"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@null"
android:padding="8dp"
android:src="@drawable/ic_sort"
app:layout_constraintBottom_toBottomOf="@+id/songTitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/songTitle"
app:layout_constraintTop_toTopOf="@id/songTitle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
@ -193,4 +206,4 @@
android:layout_height="72dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/listeners" />
</code.name.monkey.retromusic.views.insets.InsetsConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -41,6 +41,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
android:splitTrack="false"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"

View file

@ -53,6 +53,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:progressDrawable="@drawable/color_progress_seek"

View file

@ -28,10 +28,11 @@
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/progressSlider"
android:layout_width="0dp"
style="@style/MusicProgressSlider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
android:layout_weight="1"
android:paddingVertical="@dimen/seekbar_padding" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/songTotalTime"

View file

@ -172,6 +172,7 @@
android:id="@+id/progressSlider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingVertical="@dimen/seekbar_padding"
app:layout_constraintBottom_toTopOf="@+id/songInfo"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -50,6 +50,7 @@
android:layout_height="match_parent"
android:layout_toLeftOf="@id/songTotalTime"
android:layout_toRightOf="@id/songCurrentProgress"
android:paddingVertical="@dimen/seekbar_padding"
tools:ignore="RtlHardcoded,UnusedAttribute" />
</RelativeLayout>

View file

@ -28,6 +28,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -37,6 +37,7 @@
style="@style/MusicProgressSlider"
android:layout_width="0dp"
android:layout_height="match_parent"
android:paddingVertical="@dimen/seekbar_padding"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
tools:ignore="RtlHardcoded,UnusedAttribute" />

View file

@ -27,6 +27,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="8"
android:paddingVertical="@dimen/seekbar_padding"
android:splitTrack="false"
android:thumb="@drawable/switch_square"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"

View file

@ -99,6 +99,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -101,6 +101,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingVertical="@dimen/seekbar_padding"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toBottomOf="@id/titleContainer"

View file

@ -4,7 +4,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
android:background="?attr/colorSurface"
android:clickable="true"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/colorBackground"

View file

@ -30,6 +30,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -29,6 +29,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -40,6 +40,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -33,6 +33,7 @@
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded,UnusedAttribute"
android:paddingVertical="@dimen/seekbar_padding"
tools:progress="20" />
<com.google.android.material.textview.MaterialTextView

View file

@ -13,13 +13,20 @@
</androidx.viewpager.widget.ViewPager>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/cover_lyrics"
android:name="code.name.monkey.retromusic.fragments.other.CoverLyricsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
<com.bosphere.fadingedgelayout.FadingEdgeLayout
android:id="@+id/fading_edge_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fel_edge="top|bottom"
app:fel_size_bottom="80dp"
app:fel_size_top="80dp">
app:fel_size_bottom="100dp"
app:fel_size_top="100dp">
<code.name.monkey.retromusic.lyrics.CoverLrcView
android:id="@+id/lyricsView"

View file

@ -27,6 +27,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintEnd_toStartOf="@id/songTotalTime"
app:layout_constraintStart_toEndOf="@id/songCurrentProgress"

View file

@ -29,6 +29,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxHeight="2dp"
android:paddingVertical="@dimen/seekbar_padding"
android:progressDrawable="@drawable/color_progress_seek"
app:layout_constraintBottom_toBottomOf="@+id/volumeDown"
app:layout_constraintEnd_toStartOf="@+id/volumeUp"

View file

@ -20,13 +20,14 @@
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:padding="8dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_restore"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/title"
android:layout_width="0dp"

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/action_sort_order_title"
android:title="@string/sort_order_a_z" />
<item
android:id="@+id/action_sort_order_title_desc"
android:title="@string/sort_order_z_a" />
<item
android:id="@+id/action_sort_order_album"
android:title="@string/sort_order_album" />
<item
android:id="@+id/action_sort_order_year"
android:title="@string/sort_order_year" />
<item
android:id="@+id/action_sort_order_song_duration"
android:title="@string/song_duration" />
</group>
</menu>

View file

@ -146,7 +146,6 @@
<string name="currently_listening_to_x_by_x">يستمع حالياً إلى %1$s لـ %2$s</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">أسود قليلاً</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">حذف قائمة التشغيل</string>
<string name="delete_playlist_x"><![CDATA[حذف قائمة التشغيل <b>%1$s</b>؟]]></string>
<string name="delete_playlists_title">حذف قوائم التشغيل</string>

View file

@ -140,7 +140,6 @@
<string name="currently_listening_to_x_by_x">Aktuálně posloucháš %1$s od %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Tmavá</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Smazat seznam skladeb</string>
<string name="delete_playlist_x"><![CDATA[Smazat seznam skladeb <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Smazat seznamy skladeb</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Spielt zur Zeit %1$s von %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Dunkel</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Playlist löschen</string>
<string name="delete_playlist_x"><![CDATA[Playlist <b>%1$s</b> löschen?]]></string>
<string name="delete_playlists_title">Playlisten löschen</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Παίζει το %1$s από τους %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Κάπως Σκούρο</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Διαγραφή playlist</string>
<string name="delete_playlist_x"><![CDATA[Διαγραφή της λίστα αναπαραγωγής <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Διαγραφή αυτών των λιστών αναπαραγωγής</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Actualmente estás escuchando %1$s de %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Un poco Oscuro</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Eliminar lista de reproducción</string>
<string name="delete_playlist_x"><![CDATA[¿Eliminar la lista de reproducción <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Eliminar listas de reproducción</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">در حال حاضر شما به s$1% از s$2% گوش می‌دهید.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">تقریبا مشکی</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">حذف کردن پلی لیست</string>
<string name="delete_playlist_x"><![CDATA[حذف پلی لیست <b>%1$s</b>?<b>?]]></string>
<string name="delete_playlists_title">حذف کردن پلی لیست ها</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Kasalukuyang nakikinig sa %1$s ni %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Medyo Madilim</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Tanggalin ang playlist</string>
<string name="delete_playlist_x"><![CDATA[Tanggalin ang playlist na <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Tanggalin ang mga playlist</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Vous écoutez actuellement %1$s par %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Plutôt Sombre</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Supprimer la liste de lecture</string>
<string name="delete_playlist_x"><![CDATA[Supprimer la liste <b>%1$s</b> ?]]></string>
<string name="delete_playlists_title">Supprimer les listes de lecture</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Currently listening to %1$s by %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kinda Dark</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Delete playlist</string>
<string name="delete_playlist_x"><![CDATA[Delete the playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Delete playlists</string>

View file

@ -138,7 +138,6 @@
<string name="currently_listening_to_x_by_x">Currently listening to %1$s by %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kinda Dark</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Delete playlist</string>
<string name="delete_playlist_x"><![CDATA[Delete the playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Delete playlists</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Jelenleg %1$s hallgatása %2$s által.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kissé sötét</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Lejátszási lista törlése</string>
<string name="delete_playlist_x"><![CDATA[Törli a <b>%1$s</b> lejátszási listát?]]></string>
<string name="delete_playlists_title">Lejátszási listák törlése</string>

View file

@ -48,138 +48,137 @@
<string name="action_sleep_timer">Pengatur waktu tidur</string>
<string name="action_sort_order">Urut berdasarkan</string>
<string name="action_tag_editor">Pengubah label</string>
<string name="action_toggle_favorite">Toggle favorite</string>
<string name="action_toggle_shuffle">Toggle shuffle mode</string>
<string name="adaptive">Adaptive</string>
<string name="add_action">Add</string>
<string name="action_toggle_favorite">Aktifkan/nonaktifkan favorit</string>
<string name="action_toggle_shuffle">Aktifkan/nonaktifkan mode acak</string>
<string name="adaptive">Adaptif</string>
<string name="add_action">Tambahkan</string>
<string name="add_playlist_title">"Tambahkan ke daftar putar"</string>
<string name="add_time_framed_lryics">Add Time Framed Lyrics</string>
<string name="added_title_to_playing_queue">"Added 1 title to the playing queue."</string>
<string name="added_x_titles_to_playing_queue">Added %1$d titles to the playing queue.</string>
<string name="add_time_framed_lryics">Tambahkan kerangka waktu pada lirik</string>
<string name="added_title_to_playing_queue">"1 lagu ditambahkan ke antrean."</string>
<string name="added_x_titles_to_playing_queue">%1$d lagu telah ditambahkan ke antrean.</string>
<string name="album">Album</string>
<plurals name="albumSongs">
<item quantity="other">Songs</item>
<item quantity="other">Lagu</item>
</plurals>
<string name="album_artist">Album Artist</string>
<string name="albums">Albums</string>
<string name="album_artist">Artis Album</string>
<string name="albums">Album</string>
<plurals name="albums">
<item quantity="other">Albums</item>
<item quantity="other">Album</item>
</plurals>
<string name="always">Always</string>
<string name="app_share">Hey check out this cool music player at: https://play.google.com/store/apps/details?id=%s</string>
<string name="app_shortcut_shuffle_all_short">Shuffle</string>
<string name="app_shortcut_top_tracks_short">Top Tracks</string>
<string name="app_widget_big_name">Full Image</string>
<string name="app_widget_card_name">Card</string>
<string name="app_widget_classic_name">Classic</string>
<string name="always">Selalu</string>
<string name="app_share">Hey, cobalah pemutar musik keren ini di: https://play.google.com/store/apps/details?id=%s</string>
<string name="app_shortcut_shuffle_all_short">Acak</string>
<string name="app_shortcut_top_tracks_short">Lagu Teratas</string>
<string name="app_widget_big_name">Gambar penuh</string>
<string name="app_widget_card_name">Kartu</string>
<string name="app_widget_classic_name">Klasik</string>
<string name="app_widget_md3_name">MD3</string>
<string name="app_widget_small_name">Small</string>
<string name="app_widget_text_name">Minimal Text</string>
<string name="artist">Artist</string>
<string name="artists">Artists</string>
<string name="audio_fade_duration">Audio Fade Duration</string>
<string name="audio_focus_denied">Audio focus denied.</string>
<string name="audio_settings_summary">Change the sound settings and adjust the equalizer controls</string>
<string name="auto">Auto</string>
<string name="backup_restore_settings_summary">Backup and restore your settings, playlists</string>
<string name="backup_restore_title"><![CDATA[Backup & Restore]]></string>
<string name="backup_title">Backups</string>
<string name="biography">Biography</string>
<string name="black_theme_name">Just Black</string>
<string name="blacklist">Blacklist</string>
<string name="blur">Blur</string>
<string name="blur_card">Blur Card</string>
<string name="bug_report_failed">Unable to send report</string>
<string name="bug_report_failed_invalid_token">Invalid access token. Please contact the app developer.</string>
<string name="bug_report_failed_issues_not_available">Issues are not enabled for the selected repository. Please contact the app developer.</string>
<string name="bug_report_failed_unknown">An unexpected error occurred. Please contact the app developer.</string>
<string name="bug_report_failed_wrong_credentials">Wrong username or password</string>
<string name="bug_report_issue">Issue</string>
<string name="bug_report_manual">Send manually</string>
<string name="bug_report_no_description">Please enter an issue description</string>
<string name="bug_report_no_password">Please enter your valid GitHub password</string>
<string name="bug_report_no_title">Please enter an issue title</string>
<string name="bug_report_no_username">Please enter your valid GitHub username</string>
<string name="bug_report_summary">An unexpected error occurred. Sorry you found this bug, if it keeps crashing \"Clear app data\" or send an Email </string>
<string name="bug_report_use_account">Send using GitHub account</string>
<string name="buy_now">Buy now</string>
<string name="cancel_current_timer">Cancel</string>
<string name="card">Card</string>
<string name="card_color_style">Colored Card</string>
<string name="card_square">Square Card</string>
<string name="card_style">Card</string>
<string name="carousal_effect_on_now_playing_screen">Carousel effect on the now playing screen</string>
<string name="cascading">Cascading</string>
<string name="changelog">Changelog</string>
<string name="changelog_summary">Check out What\'s New</string>
<string name="choose_restore_title">Choose what to restore</string>
<string name="circle">Circle</string>
<string name="circular">Circular</string>
<string name="classic">Classic</string>
<string name="clear_action">Clear</string>
<string name="clear_blacklist">Clear blacklist</string>
<string name="clear_playing_queue">Clear queue</string>
<string name="color">Color</string>
<string name="colors">Colors</string>
<string name="composer">Composer</string>
<string name="copied_device_info_to_clipboard">Copied device info to clipboard.</string>
<string name="could_not_create_playlist">Couldn\u2019t create playlist.</string>
<string name="could_not_download_album_cover">"Couldn\u2019t download a matching album cover."</string>
<string name="could_not_restore_purchase">Could not restore purchase.</string>
<string name="could_not_scan_files">Could not scan %d files.</string>
<string name="create_action">Create</string>
<string name="create_new_backup">Create</string>
<string name="created_playlist_x">Created playlist %1$s.</string>
<string name="credit_title">Members and contributors </string>
<string name="currently_listening_to_x_by_x">Currently listening to %1$s by %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kinda Dark</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Delete playlist</string>
<string name="delete_playlist_x"><![CDATA[Delete the playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Delete playlists</string>
<string name="delete_song_title">Delete song</string>
<string name="delete_song_x"><![CDATA[Delete the song <b>%1$s</b>?]]></string>
<string name="delete_songs_title">Delete songs</string>
<string name="delete_x_playlists"><![CDATA[Delete <b>%1$d</b> playlists?]]></string>
<string name="delete_x_songs"><![CDATA[Delete <b>%1$d</b> songs?]]></string>
<string name="deleted_x_songs">Deleted %1$d songs.</string>
<string name="deleting_songs">Deleting Songs</string>
<string name="depth">Depth</string>
<string name="description">Description</string>
<string name="device_info">Device info</string>
<string name="dialog_message_set_ringtone">Allow Retro Music to modify audio settings</string>
<string name="dialog_title_set_ringtone">Set ringtone</string>
<string name="disc_hint">Disc Number</string>
<string name="do_you_want_to_clear_the_blacklist">Do you want to clear the blacklist?</string>
<string name="do_you_want_to_remove_from_the_blacklist"><![CDATA[Do you want to remove <b>%1$s</b> from the blacklist?]]></string>
<string name="donate">Donate</string>
<string name="donate_summary">If you think I deserve to get paid for my work, you can leave some money here</string>
<string name="donation_header">Buy me a:</string>
<string name="done">Done</string>
<string name="drive_mode">Drive mode</string>
<string name="edit_fab">Edit Button</string>
<string name="edit_normal_lyrics">Edit Lyrics</string>
<string name="edit_synced_lyrics">Edit Synced Lyrics</string>
<string name="empty">Empty</string>
<string name="app_widget_small_name">Kecil</string>
<string name="app_widget_text_name">Teks minimal</string>
<string name="artist">Artis</string>
<string name="artists">Artis</string>
<string name="audio_fade_duration">Durasi transisi antar lagu</string>
<string name="audio_focus_denied">Fokus suara ditolak.</string>
<string name="audio_settings_summary">Ubah pengaturan suara dan atur equalizer</string>
<string name="auto">Otomatis</string>
<string name="backup_restore_settings_summary">Cadangkan dan pulihkan pengaturan, daftar putar anda</string>
<string name="backup_restore_title"><![CDATA[Cadangkan & Pulihkan]]></string>
<string name="backup_title">Cadangkan</string>
<string name="biography">Biografi</string>
<string name="black_theme_name">Hitam Pekat</string>
<string name="blacklist">Daftar hitam</string>
<string name="blur">Keburaman</string>
<string name="blur_card">Kartu buram</string>
<string name="bug_report_failed">Tidak dapat mengirim laporan</string>
<string name="bug_report_failed_invalid_token">Token akses tidak valid. Silakan hubungi pengembang aplikasi.</string>
<string name="bug_report_failed_issues_not_available">Masalah tidak diaktifkan untuk repositori yang dipilih. Silakan hubungi pengembang aplikasi.</string>
<string name="bug_report_failed_unknown">Terjadi kesalahan yang tak terduga. Silakan hubungi pengembang aplikasi.</string>
<string name="bug_report_failed_wrong_credentials">Username atau password salah</string>
<string name="bug_report_issue">Masalah</string>
<string name="bug_report_manual">Kirim secara manual</string>
<string name="bug_report_no_description">Silakan masukkan deskripsi masalah</string>
<string name="bug_report_no_password">Mohon masukkan kata sandi Github Anda dengan benar</string>
<string name="bug_report_no_title">Harap masukkan judul masalah</string>
<string name="bug_report_no_username">Mohon masukkan nama pengguna Github Anda dengan benar</string>
<string name="bug_report_summary">Terjadi kesalahan tak terduga. Maaf jika Anda menemukan bug ini, jika terus terjadi maka \"Hapus data aplikasi\" atau kirim Email </string>
<string name="bug_report_use_account">Kirim dengan menggunakan akun Github</string>
<string name="buy_now">Beli sekarang</string>
<string name="cancel_current_timer">Batal</string>
<string name="card">Kartu</string>
<string name="card_color_style">Kartu Berwarna</string>
<string name="card_square">Kartu Persegi</string>
<string name="card_style">Kartu</string>
<string name="carousal_effect_on_now_playing_screen">Efek korsel pada layar yang sedang diputar</string>
<string name="cascading">Bergerak</string>
<string name="changelog">Daftar Log Pembaruan</string>
<string name="changelog_summary">Lihat Apa Yang Baru</string>
<string name="choose_restore_title">Pilih apa yang akan dipulihkan</string>
<string name="circle">Bulat</string>
<string name="circular">Melingkar</string>
<string name="classic">Klasik</string>
<string name="clear_action">Bersihkan</string>
<string name="clear_blacklist">Bersihkan daftar hitam</string>
<string name="clear_playing_queue">Bersihkan antrean</string>
<string name="color">Warna</string>
<string name="colors">Warna</string>
<string name="composer">Komposer</string>
<string name="copied_device_info_to_clipboard">Info perangkat tersalin ke papan klip.</string>
<string name="could_not_create_playlist">Tidak dapat membuat daftar putar.</string>
<string name="could_not_download_album_cover">"Tidak dapat mengunduh sampul album yang cocok."</string>
<string name="could_not_restore_purchase">Tidak dapat mengembalikan pembelian.</string>
<string name="could_not_scan_files">Tidak dapat memindai %d berkas.</string>
<string name="create_action">Buat</string>
<string name="create_new_backup">Buat</string>
<string name="created_playlist_x">%1$s daftar putar telah dibuat.</string>
<string name="credit_title">Member dan kontributor </string>
<string name="currently_listening_to_x_by_x">Saat ini mendengarkan %1$s oleh %2$s.</string>
<string name="custom_artist_images">Gambar Artis Kustom</string>
<string name="dark_theme_name">Agak Gelap</string>
<string name="delete_playlist_title">Hapus daftar putar</string>
<string name="delete_playlist_x"><![CDATA[Hapus daftar putar <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Hapus daftar putar</string>
<string name="delete_song_title">Hapus lagu</string>
<string name="delete_song_x"><![CDATA[Hapus lagu <b>%1$s</b>?]]></string>
<string name="delete_songs_title">Hapus lagu</string>
<string name="delete_x_playlists"><![CDATA[Hapus <b>%1$d</b> daftar putar?]]></string>
<string name="delete_x_songs"><![CDATA[Hapus <b>%1$d</b> lagu?]]></string>
<string name="deleted_x_songs">Menghapus %1$d lagu.</string>
<string name="deleting_songs">Menghapus Lagu</string>
<string name="depth">Kedalaman</string>
<string name="description">Keterangan</string>
<string name="device_info">Info perangkat</string>
<string name="dialog_message_set_ringtone">Izinkan Retro Music untuk mengubah pengaturan audio</string>
<string name="dialog_title_set_ringtone">Setel nada dering</string>
<string name="disc_hint">Nomer disk</string>
<string name="do_you_want_to_clear_the_blacklist">Apakah Anda ingin menghapus daftar hitam?</string>
<string name="do_you_want_to_remove_from_the_blacklist"><![CDATA[Apakah Anda ingin menghapus <b>%1$s</b> dari daftar hitam?]]></string>
<string name="donate">Donasi</string>
<string name="donate_summary">Jika Anda pikir saya pantas dibayar untuk pekerjaan saya, Anda dapat meninggalkan sejumlah uang di sini</string>
<string name="donation_header">Belikan saya:</string>
<string name="done">Selesai</string>
<string name="drive_mode">Mode mengemudi</string>
<string name="edit_fab">Tombol Edit</string>
<string name="edit_normal_lyrics">Edit Lirik</string>
<string name="edit_synced_lyrics">Edit Lirik Yang Disinkronkan</string>
<string name="empty">Kosong</string>
<string name="equalizer">Equalizer</string>
<string name="faq">FAQ</string>
<string name="favorites">Favorites</string>
<string name="finish_last_song">Finish last song</string>
<string name="fit">Fit</string>
<string name="flat">Flat</string>
<string name="folders">Folders</string>
<string name="follow_system">Follow system</string>
<string name="for_you">For you</string>
<string name="free">Free</string>
<string name="full">Full</string>
<string name="full_card">Full card</string>
<string name="general_settings_summary">Change the theme and colors of the app</string>
<string name="general_settings_title">Look and feel</string>
<string name="faq">Pertanyaan Umum (FAQ)</string>
<string name="favorites">Favorit</string>
<string name="finish_last_song">Selesaikan lagu terakhir</string>
<string name="fit">Paskan</string>
<string name="flat">Datar</string>
<string name="folders">Folder</string>
<string name="follow_system">Ikuti setelan sistem</string>
<string name="for_you">Untuk Anda</string>
<string name="free">Gratis</string>
<string name="full">Penuh</string>
<string name="full_card">Kartu penuh</string>
<string name="general_settings_summary">Ubah tema dan warna aplikasi</string>
<string name="general_settings_title">Tampilan dan nuansa</string>
<string name="genre">Genre</string>
<string name="genres">Genres</string>
<string name="git_hub_summary">Fork the project on GitHub</string>
<string name="gradient">Gradient</string>
<string name="genres">Genre</string>
<string name="git_hub_summary">Beri fork pada proyek di GitHub</string>
<string name="gradient">Gradasi</string>
<string name="grid_size_1">1</string>
<string name="grid_size_2">2</string>
<string name="grid_size_3">3</string>
@ -188,92 +187,92 @@
<string name="grid_size_6">6</string>
<string name="grid_size_7">7</string>
<string name="grid_size_8">8</string>
<string name="grid_style_label">Grid style</string>
<string name="help_summary">Need more help?</string>
<string name="hinge">Hinge</string>
<string name="history">History</string>
<string name="home">Home</string>
<string name="horizontal_flip">Horizontal flip</string>
<string name="image">Image</string>
<string name="image_gradient">Gradient image</string>
<string name="image_settings_summary">Change artist image download settings</string>
<string name="import_label">Import</string>
<string name="import_playlist">Import playlist</string>
<string name="import_playlist_message">It imports all playlists listed in the Android Media Store with songs, if the playlists already exists, the songs will get merged.</string>
<string name="inserted_x_songs_into_playlist_x">Inserted %1$d songs into the playlist %2$s.</string>
<string name="instagram_page_summary">Share your Retro Music setup to showcase on Instagram</string>
<string name="keyboard">Keyboard</string>
<string name="grid_style_label">Gaya kisi</string>
<string name="help_summary">Butuh bantuan lebih?</string>
<string name="hinge">Engsel</string>
<string name="history">Riwayat</string>
<string name="home">Beranda</string>
<string name="horizontal_flip">Balik horisontal</string>
<string name="image">Gambar</string>
<string name="image_gradient">Gambar bergradasi</string>
<string name="image_settings_summary">Ubah pengaturan unduhan gambar artis</string>
<string name="import_label">Impor</string>
<string name="import_playlist">Impor daftar putar</string>
<string name="import_playlist_message">Ini mengimpor semua daftar putar yang terdaftar di Android Media Store dengan lagu, jika daftar putar sudah ada, lagu akan digabungkan.</string>
<string name="inserted_x_songs_into_playlist_x">Memasukkan %1$d lagu ke dalam daftar putar %2$s.</string>
<string name="instagram_page_summary">Bagikan pengaturan Musik Retro Anda untuk dipamerkan di Instagram</string>
<string name="keyboard">Papan ketik</string>
<string name="label_bit_rate">Bitrate</string>
<string name="label_file_format">Format</string>
<string name="label_file_name">File name</string>
<string name="label_file_path">File path</string>
<string name="label_file_size">Size</string>
<string name="label_more_from">More from %s</string>
<string name="label_file_name">Nama file</string>
<string name="label_file_path">Lokasi File</string>
<string name="label_file_size">Ukuran</string>
<string name="label_more_from">Lainnya dari %s</string>
<string name="label_sampling_rate">Sampling rate</string>
<string name="label_track_length">Length</string>
<string name="labeled">Labeled</string>
<string name="last_added">Last added</string>
<string name="last_song">Last song</string>
<string name="library_categories">Library categories</string>
<string name="licenses">Licenses</string>
<string name="light_theme_name">Clearly White</string>
<string name="listeners_label">Listeners</string>
<string name="listing_files">Listing files</string>
<string name="loading_products">Loading products</string>
<string name="login">Login</string>
<string name="lyrics">Lyrics</string>
<string name="made_with_love">Made with ❤️ in India</string>
<string name="label_track_length">Durasi</string>
<string name="labeled">Berlabel</string>
<string name="last_added">Terakhir ditambahkan</string>
<string name="last_song">Lagu terakhir</string>
<string name="library_categories">Kategori perpustakaan</string>
<string name="licenses">Lisensi</string>
<string name="light_theme_name">Putih Terang</string>
<string name="listeners_label">Pendengar</string>
<string name="listing_files">Daftar file</string>
<string name="loading_products">Memuat produk</string>
<string name="login">Masuk</string>
<string name="lyrics">Lirik</string>
<string name="made_with_love">Dibuat dengan ❤️ di India</string>
<string name="material">Material</string>
<string name="md_error_label">Error</string>
<string name="md_storage_perm_error">Permission error</string>
<string name="my_name">Name</string>
<string name="my_top_tracks">Most played</string>
<string name="never">Never</string>
<string name="md_error_label">Kesalahan</string>
<string name="md_storage_perm_error">Kesalahan izin</string>
<string name="my_name">Nama</string>
<string name="my_top_tracks">Paling sering diputar</string>
<string name="never">Tidak pernah</string>
<string name="new_playlist_title">Daftar putar baru</string>
<string name="new_start_directory">%s is the new start directory.</string>
<string name="next_song">Next Song</string>
<string name="no_albums">You have no albums</string>
<string name="no_artists">You have no artists</string>
<string name="no_audio_ID">"Play a song first, then try again."</string>
<string name="no_backups_found">No Backups Found</string>
<string name="no_equalizer">No equalizer found</string>
<string name="no_genres">You have no genres</string>
<string name="no_lyrics_found">No lyrics found</string>
<string name="no_playing_queue">No songs playing</string>
<string name="no_playlists">You have no playlists</string>
<string name="no_purchase_found">No purchase found.</string>
<string name="no_results">No results</string>
<string name="no_songs">You have no songs</string>
<string name="new_start_directory">%s adalah direktori awal yang baru.</string>
<string name="next_song">Lagu berikutnya</string>
<string name="no_albums">Tidak ada album</string>
<string name="no_artists">Tidak ada artis</string>
<string name="no_audio_ID">"Putar lagu terlebih dahulu, lalu coba lagi."</string>
<string name="no_backups_found">Tidak ada cadangan yang ditemukan</string>
<string name="no_equalizer">Equalizer tidak ditemukan</string>
<string name="no_genres">Tidak ada genre</string>
<string name="no_lyrics_found">Lirik tidak ditemukan</string>
<string name="no_playing_queue">Tidak ada lagu yang diputar</string>
<string name="no_playlists">Tidak ada daftar putar</string>
<string name="no_purchase_found">Tidak ada pembelian yang ditemukan.</string>
<string name="no_results">Tidak ada hasil</string>
<string name="no_songs">Tidak ada lagu</string>
<string name="normal">Normal</string>
<string name="normal_lyrics">Normal lyrics</string>
<string name="not_listed_in_media_store"><![CDATA[<b>%s</b> is not listed in the media store.]]></string>
<string name="not_recently_played">Not recently played</string>
<string name="nothing_to_scan">Nothing to scan.</string>
<string name="nothing_to_see">Nothing to see</string>
<string name="notification">Notification</string>
<string name="notification_settings_summary">Customize the notification style</string>
<string name="now_playing">Now playing</string>
<string name="now_playing_queue">Now playing queue</string>
<string name="now_playing_summary">Customize the now playing screen</string>
<string name="now_playing_themes">9+ now playing themes</string>
<string name="only_on_wifi">Only on Wi-Fi</string>
<string name="other_settings_summary">Advanced testing features</string>
<string name="others">Other</string>
<string name="password">Password</string>
<string name="past_three_months">Past 3 months</string>
<string name="paste_lyrics_here">Paste Lyrics Here</string>
<string name="paste_timeframe_lyrics_here">Paste timeframe lyrics here</string>
<string name="peak">Peak</string>
<string name="permission_external_storage_denied">Permission to access external storage denied.</string>
<string name="permission_summary">The app needs permission to access your device storage for playing music</string>
<string name="permission_title">Storage Access</string>
<string name="permissions_denied">Permissions denied.</string>
<string name="personalize">Personalize</string>
<string name="personalize_settings_summary">Customize your now playing and UI controls</string>
<string name="pick_from_local_storage">Pick from local storage</string>
<string name="normal_lyrics">Lirik normal</string>
<string name="not_listed_in_media_store"><![CDATA[<b>%s</b> tidak terdaftar di penyimpanan media.]]></string>
<string name="not_recently_played">Tidak baru-baru ini dimainkan</string>
<string name="nothing_to_scan">Tidak ada yang di pindai.</string>
<string name="nothing_to_see">Tidak ada yang bisa dilihat</string>
<string name="notification">Pemberitahuan</string>
<string name="notification_settings_summary">Sesuaikan gaya notifikasi</string>
<string name="now_playing">Sedang diputar</string>
<string name="now_playing_queue">Sekarang memutar antrean</string>
<string name="now_playing_summary">Sesuaikan tampilan layar yang sedang diputar</string>
<string name="now_playing_themes">9+ tema pemutar sekarang</string>
<string name="only_on_wifi">Hanya di Wifi</string>
<string name="other_settings_summary">Fitur pengujian lanjutan</string>
<string name="others">Lainnya</string>
<string name="password">Kata sandi</string>
<string name="past_three_months">3 bulan terakhir</string>
<string name="paste_lyrics_here">Tempel Lirik Disini</string>
<string name="paste_timeframe_lyrics_here">Tempel kerangka waktu lirik di sini</string>
<string name="peak">Puncak</string>
<string name="permission_external_storage_denied">Izin untuk mengakses penyimpanan eksternal ditolak.</string>
<string name="permission_summary">Aplikasi memerlukan izin untuk mengakses penyimpanan perangkat Anda untuk memutar musik</string>
<string name="permission_title">Akses Penyimpanan</string>
<string name="permissions_denied">Izin ditolak.</string>
<string name="personalize">Personalisasi</string>
<string name="personalize_settings_summary">Sesuaikan pemutar dan UI Anda</string>
<string name="pick_from_local_storage">Pilih dari penyimpanan lokal</string>
<string name="pinterest_page">Pinterest</string>
<string name="pinterest_page_summary">Follow Pinterest page for Retro Music design inspiration</string>
<string name="plain">Plain</string>
<string name="pinterest_page_summary">Ikuti halaman Pinterest untuk inspirasi desain Retro Music</string>
<string name="plain">Polos</string>
<string name="playing_notification_description">The playing notification provides actions for play/pause etc.</string>
<string name="playing_notification_name">Playing notification</string>
<string name="playlist_is_empty">Playlist is empty</string>
@ -286,7 +285,7 @@
<string name="pref_header_advanced">Advanced</string>
<string name="pref_header_album">Album style</string>
<string name="pref_header_audio">Audio</string>
<string name="pref_header_blacklist">Blacklist</string>
<string name="pref_header_blacklist">Daftar hitam</string>
<string name="pref_header_controls">Controls</string>
<string name="pref_header_general">Theme</string>
<string name="pref_header_images">Images</string>
@ -337,7 +336,7 @@
<string name="pref_title_audio_ducking">Reduce volume on focus loss</string>
<string name="pref_title_audio_fade">Fade Audio</string>
<string name="pref_title_auto_download_artist_images">Auto-download artist images</string>
<string name="pref_title_blacklist">Blacklist</string>
<string name="pref_title_blacklist">Daftar hitam</string>
<string name="pref_title_bluetooth_playback">Bluetooth playback</string>
<string name="pref_title_blurred_album_art">Blur album cover</string>
<string name="pref_title_classic_notification">Classic notification design</string>
@ -428,7 +427,7 @@
<string name="share_summary">Share the app with your friends and family</string>
<string name="share_to_stories">Share to Stories</string>
<string name="show_album_artists">Show Album Artists</string>
<string name="shuffle">Shuffle</string>
<string name="shuffle">Acak</string>
<string name="simple">Simple</string>
<string name="sleep_timer_canceled">Sleep timer canceled.</string>
<string name="sleep_timer_set">Sleep timer set for %d minutes from now.</string>
@ -441,8 +440,8 @@
<string name="sort_order_a_z">Ascending</string>
<string name="sort_order_album">Album</string>
<string name="sort_order_album_artist">@string/album_artist</string>
<string name="sort_order_artist">Artist</string>
<string name="sort_order_composer">Composer</string>
<string name="sort_order_artist">Artis</string>
<string name="sort_order_composer">Komposer</string>
<string name="sort_order_date">Date added</string>
<string name="sort_order_date_modified">Date modified</string>
<string name="sort_order_num_songs">Song count</string>

View file

@ -137,7 +137,6 @@ https://play.google.com/store/apps/details?id=%s</string>
<string name="currently_listening_to_x_by_x">Ascoltando attualmente %1$s di %2$s.</string>
<string name="custom_artist_images">Immagini dell\'Artista Personalizzate</string>
<string name="dark_theme_name">Scuro</string>
<string name="databases_description">Database (Playlist, Cronologia, Più riprodotti, ecc.)</string>
<string name="delete_playlist_title">Elimina playlist</string>
<string name="delete_playlist_x"><![CDATA[Elimina la playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Elimina playlist</string>

View file

@ -135,7 +135,6 @@
<string name="currently_listening_to_x_by_x">現在、 %1$s によって %2$s で聴いています。</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">ダーク</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">プレイリストを削除</string>
<string name="delete_playlist_x"><![CDATA[プレイリスト <b>%1$s</b> を削除しますか?]]></string>
<string name="delete_playlists_title">プレイリストを削除</string>

View file

@ -134,7 +134,6 @@
<string name="currently_listening_to_x_by_x">현재 %2$s의 %1$s를 듣는 중입니다.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">카인다 다크</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">재생목록 삭제</string>
<string name="delete_playlist_x"><![CDATA[<b>%1$s</b> 재생목록을 삭제하시겠습니까?]]></string>
<string name="delete_playlists_title">재생목록 삭제</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Currently listening to %1$s by %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kinda Dark</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Delete playlist</string>
<string name="delete_playlist_x"><![CDATA[Delete the playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Delete playlists</string>

View file

@ -134,7 +134,6 @@
<string name="currently_listening_to_x_by_x">%2$s သီဆိုထားသော %1$s ကိုယခုနားထောင်နေသည်</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">အနက်ရောင်ဆန်ဆန်</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Playlist ဖျက်ခြင်း</string>
<string name="delete_playlist_x"><![CDATA[<b>%1$s</b> Playlist ကိုဖျက်မှာလား]]></string>
<string name="delete_playlists_title">Playlist များဖျက်ခြင်း</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Nu luisterend naar %1$s van %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Soort van donker</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Afspeellijst verwijderen</string>
<string name="delete_playlist_x"><![CDATA[Afspellijst <b>%1$s</b> verwijderen?]]></string>
<string name="delete_playlists_title">Afspeellijsten verwijderen</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Currently listening to %1$s by %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kinda Dark</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Delete playlist</string>
<string name="delete_playlist_x"><![CDATA[Delete the playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Delete playlists</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Currently listening to %1$s by %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Kinda Dark</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Delete playlist</string>
<string name="delete_playlist_x"><![CDATA[Delete the playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Delete playlists</string>

View file

@ -140,7 +140,6 @@
<string name="currently_listening_to_x_by_x">Aktualnie odtwarzane %1$s wykonawcy %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Dość ciemny</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Usuń playlistę</string>
<string name="delete_playlist_x"><![CDATA[Usunąć playlistę <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Usuń playlisty</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">Atualmente ouvindo %1$s por %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Meio escuro</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Excluir playlist</string>
<string name="delete_playlist_x"><![CDATA[Excluir a playlist <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Excluir playlists</string>

View file

@ -136,7 +136,6 @@
<string name="currently_listening_to_x_by_x">A ouvir %1$s de %2$s.</string>
<string name="custom_artist_images">Custom Artist Images</string>
<string name="dark_theme_name">Meio escuro</string>
<string name="databases_description">Databases (Playlists, History, Most Played, etc.)</string>
<string name="delete_playlist_title">Eliminar lista</string>
<string name="delete_playlist_x"><![CDATA[Eliminar a lista <b>%1$s</b>?]]></string>
<string name="delete_playlists_title">Eliminar listas</string>

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