all: lint with ktlint
Just lint with ktlint. It has better import checking functionality.
This commit is contained in:
parent
09823d7829
commit
06d6495dcd
77 changed files with 537 additions and 274 deletions
|
@ -120,10 +120,7 @@ dependencies {
|
|||
spotless {
|
||||
kotlin {
|
||||
target "src/**/*.kt"
|
||||
|
||||
// ktlint does checking, while ktfmt actually does formatting
|
||||
ktlint()
|
||||
ktfmt().dropboxStyle()
|
||||
licenseHeaderFile("NOTICE")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,11 @@ class AuxioApp : Application(), ImageLoaderFactory {
|
|||
.setIcon(IconCompat.createWithResource(this, R.drawable.ic_shortcut_shuffle_24))
|
||||
.setIntent(
|
||||
Intent(this, MainActivity::class.java)
|
||||
.setAction(INTENT_KEY_SHORTCUT_SHUFFLE))
|
||||
.build()))
|
||||
.setAction(INTENT_KEY_SHORTCUT_SHUFFLE)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun newImageLoader() =
|
||||
|
|
|
@ -98,9 +98,7 @@ class MainActivity : AppCompatActivity() {
|
|||
val action =
|
||||
when (intent.action) {
|
||||
Intent.ACTION_VIEW -> InternalPlayer.Action.Open(intent.data ?: return false)
|
||||
AuxioApp.INTENT_KEY_SHORTCUT_SHUFFLE -> {
|
||||
InternalPlayer.Action.ShuffleAll
|
||||
}
|
||||
AuxioApp.INTENT_KEY_SHORTCUT_SHUFFLE -> InternalPlayer.Action.ShuffleAll
|
||||
else -> return false
|
||||
}
|
||||
|
||||
|
|
|
@ -31,8 +31,6 @@ import androidx.navigation.fragment.findNavController
|
|||
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import org.oxycblt.auxio.databinding.FragmentMainBinding
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
@ -51,6 +49,8 @@ import org.oxycblt.auxio.util.getAttrColorCompat
|
|||
import org.oxycblt.auxio.util.getDimen
|
||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* A wrapper around the home fragment that shows the playback fragment and controls the more
|
||||
|
@ -89,9 +89,13 @@ class MainFragment :
|
|||
|
||||
// Send meaningful accessibility events for bottom sheets
|
||||
ViewCompat.setAccessibilityPaneTitle(
|
||||
binding.playbackSheet, context.getString(R.string.lbl_playback))
|
||||
binding.playbackSheet,
|
||||
context.getString(R.string.lbl_playback)
|
||||
)
|
||||
ViewCompat.setAccessibilityPaneTitle(
|
||||
binding.queueSheet, context.getString(R.string.lbl_queue))
|
||||
binding.queueSheet,
|
||||
context.getString(R.string.lbl_queue)
|
||||
)
|
||||
|
||||
val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior?
|
||||
if (queueSheetBehavior != null) {
|
||||
|
@ -100,7 +104,8 @@ class MainFragment :
|
|||
|
||||
unlikelyToBeNull(binding.handleWrapper).setOnClickListener {
|
||||
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED &&
|
||||
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) {
|
||||
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED
|
||||
) {
|
||||
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
|
@ -328,14 +333,16 @@ class MainFragment :
|
|||
|
||||
if (queueSheetBehavior != null &&
|
||||
queueSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED &&
|
||||
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) {
|
||||
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED
|
||||
) {
|
||||
// Collapse the queue first if it is expanded.
|
||||
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED
|
||||
return
|
||||
}
|
||||
|
||||
if (playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED &&
|
||||
playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN) {
|
||||
playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN
|
||||
) {
|
||||
// Then collapse the playback sheet.
|
||||
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED
|
||||
return
|
||||
|
@ -355,9 +362,9 @@ class MainFragment :
|
|||
|
||||
isEnabled =
|
||||
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
|
||||
queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
|
||||
exploreNavController.currentDestination?.id !=
|
||||
exploreNavController.graph.startDestinationId
|
||||
queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
|
||||
exploreNavController.currentDestination?.id !=
|
||||
exploreNavController.graph.startDestinationId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,11 @@ class AlbumDetailFragment :
|
|||
collectImmediately(detailModel.currentAlbum, ::handleItemChange)
|
||||
collectImmediately(detailModel.albumData, detailAdapter::submitList)
|
||||
collectImmediately(
|
||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||
playbackModel.song,
|
||||
playbackModel.parent,
|
||||
playbackModel.isPlaying,
|
||||
::updatePlayback
|
||||
)
|
||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||
}
|
||||
|
||||
|
@ -164,7 +168,9 @@ class AlbumDetailFragment :
|
|||
findNavController()
|
||||
.navigate(
|
||||
AlbumDetailFragmentDirections.actionShowArtist(
|
||||
unlikelyToBeNull(detailModel.currentAlbum.value).artist.uid))
|
||||
unlikelyToBeNull(detailModel.currentAlbum.value).artist.uid
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleItemChange(album: Album?) {
|
||||
|
@ -228,7 +234,8 @@ class AlbumDetailFragment :
|
|||
binding.detailRecycler.post {
|
||||
// Make sure to increment the position to make up for the detail header
|
||||
binding.detailRecycler.layoutManager?.startSmoothScroll(
|
||||
CenterSmoothScroller(requireContext(), pos))
|
||||
CenterSmoothScroller(requireContext(), pos)
|
||||
)
|
||||
|
||||
// If the recyclerview can scroll, its certain that it will have to scroll to
|
||||
// correctly center the playing item, so make sure that the Toolbar is lifted in
|
||||
|
|
|
@ -84,7 +84,11 @@ class ArtistDetailFragment :
|
|||
collectImmediately(detailModel.currentArtist, ::handleItemChange)
|
||||
collectImmediately(detailModel.artistData, detailAdapter::submitList)
|
||||
collectImmediately(
|
||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||
playbackModel.song,
|
||||
playbackModel.parent,
|
||||
playbackModel.isPlaying,
|
||||
::updatePlayback
|
||||
)
|
||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||
}
|
||||
|
||||
|
|
|
@ -29,10 +29,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import java.lang.reflect.Field
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.ui.AuxioAppBarLayout
|
||||
import org.oxycblt.auxio.util.lazyReflectedField
|
||||
import java.lang.reflect.Field
|
||||
|
||||
/**
|
||||
* An [AuxioAppBarLayout] variant that also shows the name of the toolbar whenever the detail
|
||||
|
@ -145,6 +145,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
private const val TOOLBAR_FADE_DURATION = 150L
|
||||
|
||||
private val TOOLBAR_TITLE_TEXT_FIELD: Field by
|
||||
lazyReflectedField(Toolbar::class, "mTitleTextView")
|
||||
lazyReflectedField(Toolbar::class, "mTitleTextView")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,11 @@ class GenreDetailFragment :
|
|||
collectImmediately(detailModel.currentGenre, ::handleItemChange)
|
||||
collectImmediately(detailModel.genreData, detailAdapter::submitList)
|
||||
collectImmediately(
|
||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
|
||||
playbackModel.song,
|
||||
playbackModel.parent,
|
||||
playbackModel.isPlaying,
|
||||
::updatePlayback
|
||||
)
|
||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||
}
|
||||
|
||||
|
|
|
@ -74,14 +74,16 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
|
|||
|
||||
if (song.info.bitrateKbps != null) {
|
||||
binding.detailBitrate.setText(
|
||||
getString(R.string.fmt_bitrate, song.info.bitrateKbps))
|
||||
getString(R.string.fmt_bitrate, song.info.bitrateKbps)
|
||||
)
|
||||
} else {
|
||||
binding.detailBitrate.setText(R.string.def_bitrate)
|
||||
}
|
||||
|
||||
if (song.info.sampleRate != null) {
|
||||
binding.detailSampleRate.setText(
|
||||
getString(R.string.fmt_sample_rate, song.info.sampleRate))
|
||||
getString(R.string.fmt_sample_rate, song.info.sampleRate)
|
||||
)
|
||||
} else {
|
||||
binding.detailSampleRate.setText(R.string.def_sample_rate)
|
||||
}
|
||||
|
|
|
@ -127,7 +127,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
|
|||
binding.context.getString(
|
||||
R.string.fmt_two,
|
||||
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size),
|
||||
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
|
||||
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)
|
||||
)
|
||||
|
||||
binding.detailPlayButton.setOnClickListener { listener.onPlayParent() }
|
||||
binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() }
|
||||
|
|
|
@ -42,7 +42,8 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
|||
) : IndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
|
||||
private var isPlaying = false
|
||||
|
||||
@Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size
|
||||
@Suppress("LeakingThis")
|
||||
override fun getItemCount() = differ.currentList.size
|
||||
|
||||
override fun getItemViewType(position: Int) =
|
||||
when (differ.currentList[position]) {
|
||||
|
@ -83,7 +84,8 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
|||
return item is Header || item is SortHeader
|
||||
}
|
||||
|
||||
@Suppress("LeakingThis") protected val differ = AsyncListDiffer(this, diffCallback)
|
||||
@Suppress("LeakingThis")
|
||||
protected val differ = AsyncListDiffer(this, diffCallback)
|
||||
|
||||
override val currentList: List<Item>
|
||||
get() = differ.currentList
|
||||
|
|
|
@ -36,8 +36,6 @@ import androidx.viewpager2.widget.ViewPager2
|
|||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import java.lang.reflect.Field
|
||||
import kotlin.math.abs
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentHomeBinding
|
||||
|
@ -64,6 +62,8 @@ import org.oxycblt.auxio.util.collectImmediately
|
|||
import org.oxycblt.auxio.util.getColorCompat
|
||||
import org.oxycblt.auxio.util.lazyReflectedField
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import java.lang.reflect.Field
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each
|
||||
|
@ -111,7 +111,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
binding.homeToolbar.alpha = 1f - (abs(offset.toFloat()) / (range.toFloat() / 2))
|
||||
|
||||
binding.homeContent.updatePadding(
|
||||
bottom = binding.homeAppbar.totalScrollRange + offset)
|
||||
bottom = binding.homeAppbar.totalScrollRange + offset
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +139,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) =
|
||||
homeModel.updateCurrentTab(position)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
TabLayoutMediator(binding.homeTabs, this, AdaptiveTabStrategy(context, homeModel))
|
||||
.attach()
|
||||
|
@ -197,7 +199,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
homeModel.updateCurrentSort(
|
||||
homeModel
|
||||
.getSortForDisplay(homeModel.currentTab.value)
|
||||
.withAscending(item.isChecked))
|
||||
.withAscending(item.isChecked)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Sorting option was selected, mark it as selected and update the mode
|
||||
|
@ -205,7 +208,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
homeModel.updateCurrentSort(
|
||||
homeModel
|
||||
.getSortForDisplay(homeModel.currentTab.value)
|
||||
.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId))))
|
||||
.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,7 +288,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
binding.homeTabs.isVisible = true
|
||||
toolbarParams.scrollFlags =
|
||||
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or
|
||||
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
|
||||
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -440,9 +444,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
|
||||
companion object {
|
||||
private val VIEW_PAGER_RECYCLER_FIELD: Field by
|
||||
lazyReflectedField(ViewPager2::class, "mRecyclerView")
|
||||
lazyReflectedField(ViewPager2::class, "mRecyclerView")
|
||||
private val VIEW_PAGER_TOUCH_SLOP_FIELD: Field by
|
||||
lazyReflectedField(RecyclerView::class, "mTouchSlop")
|
||||
lazyReflectedField(RecyclerView::class, "mTouchSlop")
|
||||
private const val KEY_LAST_TRANSITION_AXIS =
|
||||
BuildConfig.APPLICATION_ID + ".key.LAST_TRANSITION_AXIS"
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Bundle
|
|||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import java.util.Formatter
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||
import org.oxycblt.auxio.music.Album
|
||||
|
@ -36,6 +35,7 @@ import org.oxycblt.auxio.ui.recycler.Item
|
|||
import org.oxycblt.auxio.ui.recycler.MenuItemListener
|
||||
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||
import org.oxycblt.auxio.util.collectImmediately
|
||||
import java.util.Formatter
|
||||
|
||||
/**
|
||||
* A [HomeListFragment] for showing a list of [Album]s.
|
||||
|
@ -83,11 +83,12 @@ class AlbumListFragment : HomeListFragment<Album>() {
|
|||
val dateAddedMillis = album.dateAdded.secsToMs()
|
||||
formatterSb.setLength(0)
|
||||
DateUtils.formatDateRange(
|
||||
context,
|
||||
formatter,
|
||||
dateAddedMillis,
|
||||
dateAddedMillis,
|
||||
DateUtils.FORMAT_ABBREV_ALL)
|
||||
context,
|
||||
formatter,
|
||||
dateAddedMillis,
|
||||
dateAddedMillis,
|
||||
DateUtils.FORMAT_ABBREV_ALL
|
||||
)
|
||||
.toString()
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import android.os.Bundle
|
|||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import java.util.Formatter
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
|
@ -38,6 +37,7 @@ import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
|||
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
|
||||
import org.oxycblt.auxio.util.collectImmediately
|
||||
import org.oxycblt.auxio.util.context
|
||||
import java.util.Formatter
|
||||
|
||||
/**
|
||||
* A [HomeListFragment] for showing a list of [Song]s.
|
||||
|
@ -59,7 +59,11 @@ class SongListFragment : HomeListFragment<Song>() {
|
|||
|
||||
collectImmediately(homeModel.songs, homeAdapter::replaceList)
|
||||
collectImmediately(
|
||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback)
|
||||
playbackModel.song,
|
||||
playbackModel.parent,
|
||||
playbackModel.isPlaying,
|
||||
::handlePlayback
|
||||
)
|
||||
}
|
||||
|
||||
override fun getPopup(pos: Int): String? {
|
||||
|
@ -89,11 +93,12 @@ class SongListFragment : HomeListFragment<Song>() {
|
|||
val dateAddedMillis = song.dateAdded.secsToMs()
|
||||
formatterSb.setLength(0)
|
||||
DateUtils.formatDateRange(
|
||||
context,
|
||||
formatter,
|
||||
dateAddedMillis,
|
||||
dateAddedMillis,
|
||||
DateUtils.FORMAT_ABBREV_ALL)
|
||||
context,
|
||||
formatter,
|
||||
dateAddedMillis,
|
||||
dateAddedMillis,
|
||||
DateUtils.FORMAT_ABBREV_ALL
|
||||
)
|
||||
.toString()
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,8 @@ sealed class Tab(open val mode: DisplayMode) {
|
|||
DisplayMode.SHOW_SONGS,
|
||||
DisplayMode.SHOW_ALBUMS,
|
||||
DisplayMode.SHOW_ARTISTS,
|
||||
DisplayMode.SHOW_GENRES)
|
||||
DisplayMode.SHOW_GENRES
|
||||
)
|
||||
|
||||
/** Convert an array [tabs] into a sequence of tabs. */
|
||||
fun toSequence(tabs: Array<Tab>): Int {
|
||||
|
|
|
@ -91,7 +91,8 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
when (tab) {
|
||||
is Tab.Visible -> Tab.Invisible(tab.mode)
|
||||
is Tab.Invisible -> Tab.Visible(tab.mode)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
(requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
|
||||
|
|
|
@ -22,7 +22,6 @@ import android.graphics.Bitmap
|
|||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.util.Size as AndroidSize
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.ImageSource
|
||||
|
@ -38,8 +37,6 @@ import com.google.android.exoplayer2.MediaMetadata
|
|||
import com.google.android.exoplayer2.MetadataRetriever
|
||||
import com.google.android.exoplayer2.metadata.flac.PictureFrame
|
||||
import com.google.android.exoplayer2.metadata.id3.ApicFrame
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okio.buffer
|
||||
|
@ -48,6 +45,9 @@ import org.oxycblt.auxio.music.Album
|
|||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import android.util.Size as AndroidSize
|
||||
|
||||
/**
|
||||
* The base implementation for all image fetchers in Auxio.
|
||||
|
@ -194,7 +194,8 @@ abstract class BaseFetcher : Fetcher {
|
|||
return SourceResult(
|
||||
source = ImageSource(stream.source().buffer(), context),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.DISK)
|
||||
dataSource = DataSource.DISK
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +224,9 @@ abstract class BaseFetcher : Fetcher {
|
|||
// resolution.
|
||||
val bitmap =
|
||||
SquareFrameTransform.INSTANCE.transform(
|
||||
BitmapFactory.decodeStream(stream), mosaicFrameSize)
|
||||
BitmapFactory.decodeStream(stream),
|
||||
mosaicFrameSize
|
||||
)
|
||||
|
||||
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
|
||||
|
||||
|
@ -241,7 +244,8 @@ abstract class BaseFetcher : Fetcher {
|
|||
return DrawableResult(
|
||||
drawable = mosaicBitmap.toDrawable(context.resources),
|
||||
isSampled = true,
|
||||
dataSource = DataSource.DISK)
|
||||
dataSource = DataSource.DISK
|
||||
)
|
||||
}
|
||||
|
||||
private fun Dimension.mosaicSize(): Int {
|
||||
|
|
|
@ -70,8 +70,10 @@ class BitmapProvider(private val context: Context) {
|
|||
if (guard.check(handle)) {
|
||||
target.onCompleted(null)
|
||||
}
|
||||
})
|
||||
.transformations(SquareFrameTransform.INSTANCE))
|
||||
}
|
||||
)
|
||||
.transformations(SquareFrameTransform.INSTANCE)
|
||||
)
|
||||
|
||||
currentRequest = Request(context.imageLoader.enqueue(request.build()), target)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import coil.fetch.SourceResult
|
|||
import coil.key.Keyer
|
||||
import coil.request.Options
|
||||
import coil.size.Size
|
||||
import kotlin.math.min
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.oxycblt.auxio.music.Album
|
||||
|
@ -36,6 +35,7 @@ import org.oxycblt.auxio.music.Genre
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import kotlin.math.min
|
||||
|
||||
/** A basic keyer for music data. */
|
||||
class MusicKeyer : Keyer<Music> {
|
||||
|
@ -60,7 +60,8 @@ private constructor(private val context: Context, private val album: Album) : Ba
|
|||
SourceResult(
|
||||
source = ImageSource(stream.source().buffer(), context),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.DISK)
|
||||
dataSource = DataSource.DISK
|
||||
)
|
||||
}
|
||||
|
||||
class SongFactory : Fetcher.Factory<Song> {
|
||||
|
|
|
@ -26,11 +26,11 @@ import androidx.annotation.AttrRes
|
|||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import kotlin.math.max
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.getColorCompat
|
||||
import org.oxycblt.auxio.util.getDrawableCompat
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* View that displays the playback indicator. Nominally emulates [StyledImageView], but is much
|
||||
|
@ -101,15 +101,21 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
0f,
|
||||
0f,
|
||||
drawable.intrinsicWidth.toFloat(),
|
||||
drawable.intrinsicHeight.toFloat())
|
||||
drawable.intrinsicHeight.toFloat()
|
||||
)
|
||||
indicatorMatrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat())
|
||||
indicatorMatrix.setRectToRect(
|
||||
indicatorMatrixSrc, indicatorMatrixDst, Matrix.ScaleToFit.CENTER)
|
||||
indicatorMatrixSrc,
|
||||
indicatorMatrixDst,
|
||||
Matrix.ScaleToFit.CENTER
|
||||
)
|
||||
|
||||
// Then actually center it into the icon, which the previous call does not
|
||||
// actually do.
|
||||
indicatorMatrix.postTranslate(
|
||||
(measuredWidth - iconSize) / 2f, (measuredHeight - iconSize) / 2f)
|
||||
(measuredWidth - iconSize) / 2f,
|
||||
(measuredHeight - iconSize) / 2f
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||
val staticIcon =
|
||||
styledAttrs.getResourceId(
|
||||
R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL)
|
||||
R.styleable.StyledImageView_staticIcon,
|
||||
ResourcesCompat.ID_NULL
|
||||
)
|
||||
if (staticIcon != ResourcesCompat.ID_NULL) {
|
||||
this.staticIcon = context.getDrawableCompat(staticIcon)
|
||||
}
|
||||
|
@ -143,7 +145,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
adjustWidth,
|
||||
adjustHeight,
|
||||
bounds.width() - adjustWidth,
|
||||
bounds.height() - adjustHeight)
|
||||
bounds.height() - adjustHeight
|
||||
)
|
||||
src.draw(canvas)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,11 +21,6 @@ package org.oxycblt.auxio.music
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
|
@ -36,6 +31,11 @@ import org.oxycblt.auxio.ui.recycler.Item
|
|||
import org.oxycblt.auxio.util.inRangeOrNull
|
||||
import org.oxycblt.auxio.util.nonZeroOrNull
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// --- MUSIC MODELS ---
|
||||
|
||||
|
@ -164,13 +164,15 @@ class Song constructor(raw: Raw) : Music() {
|
|||
val path =
|
||||
Path(
|
||||
name = requireNotNull(raw.displayName) { "Invalid raw: No display name" },
|
||||
parent = requireNotNull(raw.directory) { "Invalid raw: No parent directory" })
|
||||
parent = requireNotNull(raw.directory) { "Invalid raw: No parent directory" }
|
||||
)
|
||||
|
||||
/** The mime type of the audio file. Only intended for display. */
|
||||
val mimeType =
|
||||
MimeType(
|
||||
fromExtension = requireNotNull(raw.extensionMimeType) { "Invalid raw: No mime type" },
|
||||
fromFormat = raw.formatMimeType)
|
||||
fromFormat = raw.formatMimeType
|
||||
)
|
||||
|
||||
/** The size of this audio file. */
|
||||
val size = requireNotNull(raw.size) { "Invalid raw: No size" }
|
||||
|
@ -234,11 +236,12 @@ class Song constructor(raw: Raw) : Music() {
|
|||
date = raw.date,
|
||||
releaseType = raw.albumReleaseType,
|
||||
rawArtist =
|
||||
if (albumArtistName != null) {
|
||||
Artist.Raw(albumArtistName, albumArtistSortName)
|
||||
} else {
|
||||
Artist.Raw(artistName, artistSortName)
|
||||
})
|
||||
if (albumArtistName != null) {
|
||||
Artist.Raw(albumArtistName, albumArtistSortName)
|
||||
} else {
|
||||
Artist.Raw(artistName, artistSortName)
|
||||
}
|
||||
)
|
||||
|
||||
val _rawGenres = raw.genreNames?.map { Genre.Raw(it) } ?: listOf(Genre.Raw(null))
|
||||
|
||||
|
@ -505,7 +508,9 @@ fun MessageDigest.update(n: Long?) {
|
|||
n.shr(32).toByte(),
|
||||
n.shr(40).toByte(),
|
||||
n.shl(48).toByte(),
|
||||
n.shr(56).toByte()))
|
||||
n.shr(56).toByte()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -535,7 +540,8 @@ fun ByteArray.toUuid(): UUID {
|
|||
.or(get(12).toLong().and(0xFF).shl(24))
|
||||
.or(get(13).toLong().and(0xFF).shl(16))
|
||||
.or(get(14).toLong().and(0xFF).shl(8))
|
||||
.or(get(15).toLong().and(0xFF)))
|
||||
.or(get(15).toLong().and(0xFF))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -628,7 +634,8 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
|||
companion object {
|
||||
private val ISO8601_REGEX =
|
||||
Regex(
|
||||
"""^(\d{4,})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2}))?)?)?)?)?$""")
|
||||
"""^(\d{4,})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2}))?)?)?)?)?$"""
|
||||
)
|
||||
|
||||
fun from(year: Int) = fromTokens(listOf(year))
|
||||
|
||||
|
|
|
@ -97,7 +97,8 @@ class MusicStore private constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST") fun <T : Music> find(uid: Music.UID): T? = uidMap[uid] as? T
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Music> find(uid: Music.UID): T? = uidMap[uid] as? T
|
||||
|
||||
/** Sanitize an old item to find the corresponding item in a new library. */
|
||||
fun sanitize(song: Song) = find<Song>(song.uid)
|
||||
|
@ -117,7 +118,7 @@ class MusicStore private constructor() {
|
|||
/** Find a song for a [uri]. */
|
||||
fun findSongForUri(context: Context, uri: Uri) =
|
||||
context.contentResolverSafe.useQuery(uri, arrayOf(OpenableColumns.DISPLAY_NAME)) {
|
||||
cursor ->
|
||||
cursor ->
|
||||
cursor.moveToFirst()
|
||||
|
||||
val displayName =
|
||||
|
|
|
@ -24,9 +24,9 @@ import android.database.Cursor
|
|||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.text.format.DateUtils
|
||||
import java.util.UUID
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import java.util.UUID
|
||||
|
||||
/** Shortcut for making a [ContentResolver] query with less superfluous arguments. */
|
||||
fun ContentResolver.queryCursor(
|
||||
|
|
|
@ -26,10 +26,10 @@ import android.os.storage.StorageVolume
|
|||
import android.provider.MediaStore
|
||||
import android.webkit.MimeTypeMap
|
||||
import com.google.android.exoplayer2.util.MimeTypes
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.lazyReflectedMethod
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
|
||||
/** A path to a file. [name] is the stripped file name, [parent] is the parent path. */
|
||||
data class Path(val name: String, val parent: Directory)
|
||||
|
@ -66,7 +66,9 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
|
|||
|
||||
fun from(volume: StorageVolume, relativePath: String) =
|
||||
Directory(
|
||||
volume, relativePath.removePrefix(File.separator).removeSuffix(File.separator))
|
||||
volume,
|
||||
relativePath.removePrefix(File.separator).removeSuffix(File.separator)
|
||||
)
|
||||
|
||||
/**
|
||||
* Converts an opaque document uri in the form of VOLUME:PATH into a [Directory]. This is a
|
||||
|
@ -91,14 +93,15 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
|
|||
|
||||
@Suppress("NewApi")
|
||||
private val SM_API21_GET_VOLUME_LIST_METHOD: Method by
|
||||
lazyReflectedMethod(StorageManager::class, "getVolumeList")
|
||||
lazyReflectedMethod(StorageManager::class, "getVolumeList")
|
||||
|
||||
@Suppress("NewApi")
|
||||
private val SV_API21_GET_PATH_METHOD: Method by lazyReflectedMethod(StorageVolume::class, "getPath")
|
||||
|
||||
/** The "primary" storage volume containing the OS. May be an SD Card. */
|
||||
val StorageManager.primaryStorageVolumeCompat: StorageVolume
|
||||
@Suppress("NewApi") get() = primaryStorageVolume
|
||||
@Suppress("NewApi")
|
||||
get() = primaryStorageVolume
|
||||
|
||||
/**
|
||||
* A list of recognized volumes, retrieved in a compatible manner. Note that these volumes may be
|
||||
|
@ -135,11 +138,13 @@ fun StorageVolume.getDescriptionCompat(context: Context): String = getDescriptio
|
|||
|
||||
/** If this volume is the primary volume. May still be removable storage. */
|
||||
val StorageVolume.isPrimaryCompat: Boolean
|
||||
@SuppressLint("NewApi") get() = isPrimary
|
||||
@SuppressLint("NewApi")
|
||||
get() = isPrimary
|
||||
|
||||
/** If this volume is emulated. */
|
||||
val StorageVolume.isEmulatedCompat: Boolean
|
||||
@SuppressLint("NewApi") get() = isEmulated
|
||||
@SuppressLint("NewApi")
|
||||
get() = isEmulated
|
||||
|
||||
/**
|
||||
* If this volume corresponds to "Internal shared storage", represented in document URIs as
|
||||
|
@ -150,11 +155,13 @@ val StorageVolume.isInternalCompat: Boolean
|
|||
|
||||
/** Returns the UUID of the volume in a compatible manner. */
|
||||
val StorageVolume.uuidCompat: String?
|
||||
@SuppressLint("NewApi") get() = uuid
|
||||
@SuppressLint("NewApi")
|
||||
get() = uuid
|
||||
|
||||
/** Returns the state of the volume in a compatible manner. */
|
||||
val StorageVolume.stateCompat: String
|
||||
@SuppressLint("NewApi") get() = state
|
||||
@SuppressLint("NewApi")
|
||||
get() = state
|
||||
|
||||
/**
|
||||
* Returns the name of this volume as it is used in [MediaStore]. This will be
|
||||
|
|
|
@ -99,7 +99,8 @@ class MusicDirsDialog :
|
|||
dirs =
|
||||
MusicDirs(
|
||||
pendingDirs.mapNotNull { Directory.fromDocumentUri(storageManager, it) },
|
||||
savedInstanceState.getBoolean(KEY_PENDING_MODE))
|
||||
savedInstanceState.getBoolean(KEY_PENDING_MODE)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +113,8 @@ class MusicDirsDialog :
|
|||
R.id.dirs_mode_include
|
||||
} else {
|
||||
R.id.dirs_mode_exclude
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
updateMode()
|
||||
addOnButtonCheckedListener { _, _, _ -> updateMode() }
|
||||
|
@ -122,7 +124,9 @@ class MusicDirsDialog :
|
|||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putStringArrayList(
|
||||
KEY_PENDING_DIRS, ArrayList(dirAdapter.dirs.map { it.toString() }))
|
||||
KEY_PENDING_DIRS,
|
||||
ArrayList(dirAdapter.dirs.map { it.toString() })
|
||||
)
|
||||
outState.putBoolean(KEY_PENDING_MODE, isInclude(requireBinding()))
|
||||
}
|
||||
|
||||
|
@ -156,7 +160,9 @@ class MusicDirsDialog :
|
|||
// Turn the raw URI into a document tree URI
|
||||
val docUri =
|
||||
DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri, DocumentsContract.getTreeDocumentId(uri))
|
||||
uri,
|
||||
DocumentsContract.getTreeDocumentId(uri)
|
||||
)
|
||||
|
||||
// Turn it into a semi-usable path
|
||||
val treeUri = DocumentsContract.getTreeDocumentId(docUri)
|
||||
|
|
|
@ -31,3 +31,5 @@ class CacheLayer {
|
|||
|
||||
fun maybePopulateCachedRaw(raw: Song.Raw) = false
|
||||
}
|
||||
|
||||
// TODO: Make raw naming consistent (always rawSong(s), not raw)
|
||||
|
|
|
@ -26,7 +26,6 @@ import android.provider.MediaStore
|
|||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.getStringOrNull
|
||||
import java.io.File
|
||||
import org.oxycblt.auxio.music.Date
|
||||
import org.oxycblt.auxio.music.Directory
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
@ -38,6 +37,7 @@ import org.oxycblt.auxio.settings.Settings
|
|||
import org.oxycblt.auxio.util.contentResolverSafe
|
||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import java.io.File
|
||||
|
||||
/*
|
||||
* This file acts as the base for most the black magic required to get a remotely sensible music
|
||||
|
@ -167,11 +167,13 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
|||
|
||||
val cursor =
|
||||
requireNotNull(
|
||||
context.contentResolverSafe.queryCursor(
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
projection,
|
||||
selector,
|
||||
args.toTypedArray())) { "Content resolver failure: No Cursor returned" }
|
||||
context.contentResolverSafe.queryCursor(
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
projection,
|
||||
selector,
|
||||
args.toTypedArray()
|
||||
)
|
||||
) { "Content resolver failure: No Cursor returned" }
|
||||
.also { cursor = it }
|
||||
|
||||
idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
|
||||
|
@ -250,7 +252,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
|||
MediaStore.Audio.AudioColumns.ALBUM,
|
||||
MediaStore.Audio.AudioColumns.ALBUM_ID,
|
||||
MediaStore.Audio.AudioColumns.ARTIST,
|
||||
AUDIO_COLUMN_ALBUM_ARTIST)
|
||||
AUDIO_COLUMN_ALBUM_ARTIST
|
||||
)
|
||||
|
||||
abstract val dirSelector: String
|
||||
abstract fun addDirToSelectorArgs(dir: Directory, args: MutableList<String>): Boolean
|
||||
|
@ -287,8 +290,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
|||
raw.artistNames =
|
||||
cursor.getString(artistIndex).run {
|
||||
if (this != MediaStore.UNKNOWN_STRING) {
|
||||
// While we can't natively parse multi-value tags,
|
||||
// from MediaStore itself, we can still parse by user-defined separators.
|
||||
// While we can't natively parse multi-value tags from MediaStore itself, we
|
||||
// can still parse by user-defined separators.
|
||||
maybeParseSeparators(settings)
|
||||
} else {
|
||||
null
|
||||
|
@ -412,7 +415,8 @@ open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
|
|||
super.projection +
|
||||
arrayOf(
|
||||
MediaStore.Audio.AudioColumns.VOLUME_NAME,
|
||||
MediaStore.Audio.AudioColumns.RELATIVE_PATH)
|
||||
MediaStore.Audio.AudioColumns.RELATIVE_PATH
|
||||
)
|
||||
|
||||
override val dirSelector: String
|
||||
get() =
|
||||
|
@ -496,7 +500,8 @@ class Api30MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
|
|||
super.projection +
|
||||
arrayOf(
|
||||
MediaStore.Audio.AudioColumns.CD_TRACK_NUMBER,
|
||||
MediaStore.Audio.AudioColumns.DISC_NUMBER)
|
||||
MediaStore.Audio.AudioColumns.DISC_NUMBER
|
||||
)
|
||||
|
||||
override fun buildRaw(cursor: Cursor, raw: Song.Raw) {
|
||||
super.buildRaw(cursor, raw)
|
||||
|
|
|
@ -116,7 +116,8 @@ class Task(context: Context, private val settings: Settings, private val raw: So
|
|||
private val future =
|
||||
MetadataRetriever.retrieveMetadata(
|
||||
context,
|
||||
MediaItem.fromUri(requireNotNull(raw.mediaStoreId) { "Invalid raw: No id" }.audioUri))
|
||||
MediaItem.fromUri(requireNotNull(raw.mediaStoreId) { "Invalid raw: No id" }.audioUri)
|
||||
)
|
||||
|
||||
/**
|
||||
* Get the song that this task is trying to complete. If the task is still busy, this will
|
||||
|
@ -213,9 +214,11 @@ class Task(context: Context, private val settings: Settings, private val raw: So
|
|||
// 3. ID3v2.4 Release Date, as it is the second most common date type
|
||||
// 4. ID3v2.3 Original Date, as it is like #1
|
||||
// 5. ID3v2.3 Release Year, as it is the most common date type
|
||||
(tags["TDOR"]?.run { get(0).parseTimestamp() }
|
||||
(
|
||||
tags["TDOR"]?.run { get(0).parseTimestamp() }
|
||||
?: tags["TDRC"]?.run { get(0).parseTimestamp() }
|
||||
?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags))
|
||||
?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags)
|
||||
)
|
||||
?.let { raw.date = it }
|
||||
|
||||
// (Sort) Album
|
||||
|
@ -279,9 +282,11 @@ class Task(context: Context, private val settings: Settings, private val raw: So
|
|||
// 2. Date, as it is the most common date type
|
||||
// 3. Year, as old vorbis tags tended to use this (I know this because it's the only
|
||||
// tag that android supports, so it must be 15 years old or more!)
|
||||
(tags["ORIGINALDATE"]?.run { get(0).parseTimestamp() }
|
||||
(
|
||||
tags["ORIGINALDATE"]?.run { get(0).parseTimestamp() }
|
||||
?: tags["DATE"]?.run { get(0).parseTimestamp() }
|
||||
?: tags["YEAR"]?.run { get(0).parseYear() })
|
||||
?: tags["YEAR"]?.run { get(0).parseYear() }
|
||||
)
|
||||
?.let { raw.date = it }
|
||||
|
||||
// (Sort) Album
|
||||
|
|
|
@ -370,4 +370,5 @@ private val GENRE_TABLE =
|
|||
"Psybient",
|
||||
|
||||
// Auxio's extensions (Future garage is also based and deserves a slot)
|
||||
"Future Garage")
|
||||
"Future Garage"
|
||||
)
|
||||
|
|
|
@ -152,7 +152,8 @@ class Indexer {
|
|||
if (library != null) {
|
||||
logD(
|
||||
"Music indexing completed successfully in " +
|
||||
"${System.currentTimeMillis() - start}ms")
|
||||
"${System.currentTimeMillis() - start}ms"
|
||||
)
|
||||
Response.Ok(library)
|
||||
} else {
|
||||
logE("No music found")
|
||||
|
|
|
@ -58,7 +58,8 @@ class IndexingNotification(private val context: Context) :
|
|||
if (indexing.current % 50 == 0) {
|
||||
logD("Updating state to $indexing")
|
||||
setContentText(
|
||||
context.getString(R.string.fmt_indexing, indexing.current, indexing.total))
|
||||
context.getString(R.string.fmt_indexing, indexing.current, indexing.total)
|
||||
)
|
||||
setProgress(indexing.total, indexing.current, false)
|
||||
return true
|
||||
}
|
||||
|
@ -88,4 +89,6 @@ class ObservingNotification(context: Context) : ServiceNotification(context, IND
|
|||
|
||||
private val INDEXER_CHANNEL =
|
||||
ServiceNotification.ChannelInfo(
|
||||
id = BuildConfig.APPLICATION_ID + ".channel.INDEXER", nameRes = R.string.lbl_indexer)
|
||||
id = BuildConfig.APPLICATION_ID + ".channel.INDEXER",
|
||||
nameRes = R.string.lbl_indexer
|
||||
)
|
||||
|
|
|
@ -78,7 +78,9 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
|
|||
wakeLock =
|
||||
getSystemServiceCompat(PowerManager::class)
|
||||
.newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":IndexerService")
|
||||
PowerManager.PARTIAL_WAKE_LOCK,
|
||||
BuildConfig.APPLICATION_ID + ":IndexerService"
|
||||
)
|
||||
|
||||
settings = Settings(this, this)
|
||||
indexerContentObserver = SystemContentObserver()
|
||||
|
@ -126,7 +128,8 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
|
|||
when (state) {
|
||||
is Indexer.State.Complete -> {
|
||||
if (state.response is Indexer.Response.Ok &&
|
||||
state.response.library != musicStore.library) {
|
||||
state.response.library != musicStore.library
|
||||
) {
|
||||
logD("Applying new library")
|
||||
|
||||
val newLibrary = state.response.library
|
||||
|
@ -236,7 +239,10 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
|
|||
) : ContentObserver(handler), Runnable {
|
||||
init {
|
||||
contentResolverSafe.registerContentObserver(
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, this)
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
true,
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
fun release() {
|
||||
|
|
|
@ -122,7 +122,9 @@ class PlaybackPanelFragment :
|
|||
val equalizerIntent =
|
||||
Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL)
|
||||
.putExtra(
|
||||
AudioEffect.EXTRA_AUDIO_SESSION, playbackModel.currentAudioSessionId)
|
||||
AudioEffect.EXTRA_AUDIO_SESSION,
|
||||
playbackModel.currentAudioSessionId
|
||||
)
|
||||
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
|
||||
|
||||
try {
|
||||
|
|
|
@ -60,5 +60,7 @@ class PlaybackSheetBehavior<V : View>(context: Context, attributeSet: AttributeS
|
|||
MaterialShapeDrawable(sheetBackgroundDrawable.shapeAppearanceModel).apply {
|
||||
fillColor = sheetBackgroundDrawable.fillColor
|
||||
},
|
||||
sheetBackgroundDrawable))
|
||||
sheetBackgroundDrawable
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -20,11 +20,11 @@ package org.oxycblt.auxio.playback
|
|||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlin.math.max
|
||||
import org.oxycblt.auxio.databinding.ViewSeekBarBinding
|
||||
import org.oxycblt.auxio.music.formatDurationDs
|
||||
import org.oxycblt.auxio.util.inflater
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* A wrapper around [Slider] that shows not only position and duration values, but also basically
|
||||
|
|
|
@ -131,7 +131,9 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
|
|||
fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface)
|
||||
elevation = binding.context.getDimen(R.dimen.elevation_normal)
|
||||
},
|
||||
backgroundDrawable))
|
||||
backgroundDrawable
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
|
|
|
@ -42,7 +42,9 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
val queueHolder = viewHolder as QueueSongViewHolder
|
||||
return if (queueHolder.isEnabled) {
|
||||
makeFlag(
|
||||
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or
|
||||
ItemTouchHelper.ACTION_STATE_DRAG,
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||
) or
|
||||
makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
|
||||
} else {
|
||||
0
|
||||
|
@ -132,7 +134,9 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
|
|||
target: RecyclerView.ViewHolder
|
||||
) =
|
||||
playbackModel.moveQueueDataItems(
|
||||
viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
|
||||
viewHolder.bindingAdapterPosition,
|
||||
target.bindingAdapterPosition
|
||||
)
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition)
|
||||
|
|
|
@ -61,13 +61,18 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
|||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
invalidateDivider()
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// --- VIEWMODEL SETUP ----
|
||||
|
||||
collectImmediately(
|
||||
queueModel.queue, queueModel.index, playbackModel.isPlaying, ::updateQueue)
|
||||
queueModel.queue,
|
||||
queueModel.index,
|
||||
playbackModel.isPlaying,
|
||||
::updateQueue
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroyBinding(binding: FragmentQueueBinding) {
|
||||
|
@ -97,7 +102,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
|||
|
||||
binding.queueDivider.isInvisible =
|
||||
(binding.queueRecycler.layoutManager as LinearLayoutManager)
|
||||
.findFirstCompletelyVisibleItemPosition() < 1
|
||||
.findFirstCompletelyVisibleItemPosition() < 1
|
||||
|
||||
queueModel.finishReplace()
|
||||
|
||||
|
@ -122,6 +127,6 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
|
|||
val binding = requireBinding()
|
||||
binding.queueDivider.isInvisible =
|
||||
(binding.queueRecycler.layoutManager as LinearLayoutManager)
|
||||
.findFirstCompletelyVisibleItemPosition() < 1
|
||||
.findFirstCompletelyVisibleItemPosition() < 1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,10 @@ class QueueSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?
|
|||
val bars = insets.systemBarInsetsCompat
|
||||
expandedOffset = bars.top + barHeight + barSpacing
|
||||
return insets.replaceSystemBarInsetsCompat(
|
||||
bars.left, bars.top, bars.right, expandedOffset + bars.bottom)
|
||||
bars.left,
|
||||
bars.top,
|
||||
bars.right,
|
||||
expandedOffset + bars.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
/** Remove a queue item using it's recyclerview adapter index. */
|
||||
fun removeQueueDataItem(adapterIndex: Int) {
|
||||
if (adapterIndex <= playbackManager.index ||
|
||||
adapterIndex !in playbackManager.queue.indices) {
|
||||
adapterIndex !in playbackManager.queue.indices
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -21,13 +21,13 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlin.math.abs
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogPreAmpBinding
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.context
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* The dialog for customizing the ReplayGain pre-amp values.
|
||||
|
|
|
@ -24,13 +24,13 @@ import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
|||
import com.google.android.exoplayer2.metadata.Metadata
|
||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
|
||||
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.pow
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* An [AudioProcessor] that handles ReplayGain values and their amplification of the audio stream.
|
||||
|
|
|
@ -92,7 +92,8 @@ interface InternalPlayer {
|
|||
// Not advancing, so don't move the position.
|
||||
0f
|
||||
},
|
||||
creationTime)
|
||||
creationTime
|
||||
)
|
||||
|
||||
// Equality ignores the creation time to prevent functionally
|
||||
// identical states from being equal.
|
||||
|
@ -119,7 +120,8 @@ interface InternalPlayer {
|
|||
// main playing value is paused.
|
||||
isPlaying && isAdvancing,
|
||||
positionMs,
|
||||
SystemClock.elapsedRealtime())
|
||||
SystemClock.elapsedRealtime()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,8 @@ class PlaybackStateDatabase private constructor(context: Context) :
|
|||
queue = queue,
|
||||
positionMs = rawState.positionMs,
|
||||
repeatMode = rawState.repeatMode,
|
||||
isShuffled = rawState.isShuffled)
|
||||
isShuffled = rawState.isShuffled
|
||||
)
|
||||
}
|
||||
|
||||
private fun readRawState(): RawState? {
|
||||
|
@ -139,11 +140,12 @@ class PlaybackStateDatabase private constructor(context: Context) :
|
|||
index = cursor.getInt(indexIndex),
|
||||
positionMs = cursor.getLong(posIndex),
|
||||
repeatMode = RepeatMode.fromIntCode(cursor.getInt(repeatModeIndex))
|
||||
?: RepeatMode.NONE,
|
||||
?: RepeatMode.NONE,
|
||||
isShuffled = cursor.getInt(shuffleIndex) == 1,
|
||||
songUid = Music.UID.fromString(cursor.getString(songUidIndex))
|
||||
?: return@queryAll null,
|
||||
parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString))
|
||||
?: return@queryAll null,
|
||||
parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +181,8 @@ class PlaybackStateDatabase private constructor(context: Context) :
|
|||
repeatMode = state.repeatMode,
|
||||
isShuffled = state.isShuffled,
|
||||
songUid = state.queue[state.index].uid,
|
||||
parentUid = state.parent?.uid)
|
||||
parentUid = state.parent?.uid
|
||||
)
|
||||
|
||||
writeRawState(rawState)
|
||||
writeQueue(state.queue)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.oxycblt.auxio.playback.state
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
|
@ -31,6 +30,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager.Callback
|
|||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Master class (and possible god object) for the playback state.
|
||||
|
@ -491,7 +491,8 @@ class PlaybackStateManager private constructor() {
|
|||
queue = _queue,
|
||||
positionMs = playerState.calculateElapsedPosition(),
|
||||
isShuffled = isShuffled,
|
||||
repeatMode = repeatMode)
|
||||
repeatMode = repeatMode
|
||||
)
|
||||
|
||||
// --- CALLBACKS ---
|
||||
|
||||
|
|
|
@ -140,16 +140,19 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
.putText(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
|
||||
.putText(
|
||||
MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST,
|
||||
song.album.artist.resolveName(context))
|
||||
song.album.artist.resolveName(context)
|
||||
)
|
||||
.putText(MediaMetadataCompat.METADATA_KEY_AUTHOR, artist)
|
||||
.putText(MediaMetadataCompat.METADATA_KEY_COMPOSER, artist)
|
||||
.putText(MediaMetadataCompat.METADATA_KEY_WRITER, artist)
|
||||
.putText(
|
||||
MediaMetadataCompat.METADATA_KEY_GENRE,
|
||||
song.genres.joinToString { it.resolveName(context) })
|
||||
song.genres.joinToString { it.resolveName(context) }
|
||||
)
|
||||
.putText(
|
||||
METADATA_KEY_PARENT,
|
||||
parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs))
|
||||
parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs)
|
||||
)
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.durationMs)
|
||||
|
||||
song.track?.let {
|
||||
|
@ -186,7 +189,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
notification.updateMetadata(metadata)
|
||||
callback.onPostNotification(notification, PostingReason.METADATA)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateQueue(queue: List<Song>) {
|
||||
|
@ -223,7 +227,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
RepeatMode.NONE -> PlaybackStateCompat.REPEAT_MODE_NONE
|
||||
RepeatMode.TRACK -> PlaybackStateCompat.REPEAT_MODE_ONE
|
||||
RepeatMode.ALL -> PlaybackStateCompat.REPEAT_MODE_ALL
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
invalidateSecondaryAction()
|
||||
}
|
||||
|
@ -234,7 +239,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
PlaybackStateCompat.SHUFFLE_MODE_ALL
|
||||
} else {
|
||||
PlaybackStateCompat.SHUFFLE_MODE_NONE
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
invalidateSecondaryAction()
|
||||
}
|
||||
|
@ -320,7 +326,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
playbackManager.reshuffle(
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL ||
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP,
|
||||
settings)
|
||||
settings
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSkipToQueueItem(id: Long) {
|
||||
|
@ -368,19 +375,22 @@ class MediaSessionComponent(private val context: Context, private val callback:
|
|||
R.drawable.ic_shuffle_on_24
|
||||
} else {
|
||||
R.drawable.ic_shuffle_off_24
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
PlaybackStateCompat.CustomAction.Builder(
|
||||
PlaybackService.ACTION_INC_REPEAT_MODE,
|
||||
context.getString(R.string.desc_change_repeat),
|
||||
playbackManager.repeatMode.icon)
|
||||
playbackManager.repeatMode.icon
|
||||
)
|
||||
}
|
||||
|
||||
val exitAction =
|
||||
PlaybackStateCompat.CustomAction.Builder(
|
||||
PlaybackService.ACTION_EXIT,
|
||||
context.getString(R.string.desc_exit),
|
||||
R.drawable.ic_close_24)
|
||||
PlaybackService.ACTION_EXIT,
|
||||
context.getString(R.string.desc_exit),
|
||||
R.drawable.ic_close_24
|
||||
)
|
||||
.build()
|
||||
|
||||
state.addCustomAction(extraAction.build())
|
||||
|
|
|
@ -52,10 +52,12 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
|
|||
|
||||
addAction(buildRepeatAction(context, RepeatMode.NONE))
|
||||
addAction(
|
||||
buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24))
|
||||
buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24)
|
||||
)
|
||||
addAction(buildPlayPauseAction(context, true))
|
||||
addAction(
|
||||
buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24))
|
||||
buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24)
|
||||
)
|
||||
addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_close_24))
|
||||
|
||||
setStyle(MediaStyle().setMediaSession(sessionToken).setShowActionsInCompactView(1, 2, 3))
|
||||
|
@ -131,7 +133,10 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
|
|||
): NotificationCompat.Action {
|
||||
val action =
|
||||
NotificationCompat.Action.Builder(
|
||||
iconRes, actionName, context.newBroadcastPendingIntent(actionName))
|
||||
iconRes,
|
||||
actionName,
|
||||
context.newBroadcastPendingIntent(actionName)
|
||||
)
|
||||
|
||||
return action.build()
|
||||
}
|
||||
|
@ -140,6 +145,7 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
|
|||
val CHANNEL_INFO =
|
||||
ChannelInfo(
|
||||
id = BuildConfig.APPLICATION_ID + ".channel.PLAYBACK",
|
||||
nameRes = R.string.lbl_playback)
|
||||
nameRes = R.string.lbl_playback
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer
|
|||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
|
||||
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
|
||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
|
||||
import kotlin.math.max
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -58,6 +57,7 @@ import org.oxycblt.auxio.ui.system.ForegroundManager
|
|||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.widgets.WidgetComponent
|
||||
import org.oxycblt.auxio.widgets.WidgetProvider
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* A service that manages the system-side aspects of playback, such as:
|
||||
|
@ -122,8 +122,10 @@ class PlaybackService :
|
|||
handler,
|
||||
audioListener,
|
||||
AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES,
|
||||
replayGainProcessor),
|
||||
LibflacAudioRenderer(handler, audioListener, replayGainProcessor))
|
||||
replayGainProcessor
|
||||
),
|
||||
LibflacAudioRenderer(handler, audioListener, replayGainProcessor)
|
||||
)
|
||||
}
|
||||
|
||||
// Enable constant bitrate seeking so that certain MP3s/AACs are seekable
|
||||
|
@ -138,7 +140,8 @@ class PlaybackService :
|
|||
.setUsage(C.USAGE_MEDIA)
|
||||
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
|
||||
.build(),
|
||||
true)
|
||||
true
|
||||
)
|
||||
.build()
|
||||
|
||||
player.addListener(this)
|
||||
|
@ -278,7 +281,10 @@ class PlaybackService :
|
|||
override val currentState: InternalPlayer.State
|
||||
get() =
|
||||
InternalPlayer.State.new(
|
||||
player.playWhenReady, player.isPlaying, max(player.currentPosition, 0))
|
||||
player.playWhenReady,
|
||||
player.isPlaying,
|
||||
max(player.currentPosition, 0)
|
||||
)
|
||||
|
||||
override fun loadSong(song: Song?, play: Boolean) {
|
||||
if (song == null) {
|
||||
|
@ -315,7 +321,8 @@ class PlaybackService :
|
|||
Intent(event)
|
||||
.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName)
|
||||
.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId)
|
||||
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC))
|
||||
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
|
||||
)
|
||||
}
|
||||
|
||||
/** Stop the foreground state and hide the notification */
|
||||
|
@ -348,7 +355,9 @@ class PlaybackService :
|
|||
is InternalPlayer.Action.RestoreState -> {
|
||||
restoreScope.launch {
|
||||
playbackManager.restoreState(
|
||||
PlaybackStateDatabase.getInstance(this@PlaybackService), false)
|
||||
PlaybackStateDatabase.getInstance(this@PlaybackService),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
is InternalPlayer.Action.ShuffleAll -> {
|
||||
|
@ -401,7 +410,8 @@ class PlaybackService :
|
|||
override fun onSettingChanged(key: String) {
|
||||
if (key == getString(R.string.set_key_replay_gain) ||
|
||||
key == getString(R.string.set_key_pre_amp_with) ||
|
||||
key == getString(R.string.set_key_pre_amp_without)) {
|
||||
key == getString(R.string.set_key_pre_amp_without)
|
||||
) {
|
||||
onTracksChanged(player.currentTracks)
|
||||
}
|
||||
}
|
||||
|
@ -463,7 +473,8 @@ class PlaybackService :
|
|||
private fun maybeResumeFromPlug() {
|
||||
if (playbackManager.song != null &&
|
||||
settings.headsetAutoplay &&
|
||||
initialHeadsetPlugEventHandled) {
|
||||
initialHeadsetPlugEventHandled
|
||||
) {
|
||||
logD("Device connected, resuming")
|
||||
playbackManager.changePlaying(true)
|
||||
}
|
||||
|
|
|
@ -109,7 +109,11 @@ class SearchFragment :
|
|||
|
||||
collectImmediately(searchModel.searchResults, ::handleResults)
|
||||
collectImmediately(
|
||||
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback)
|
||||
playbackModel.song,
|
||||
playbackModel.parent,
|
||||
playbackModel.isPlaying,
|
||||
::handlePlayback
|
||||
)
|
||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||
}
|
||||
|
||||
|
@ -175,7 +179,8 @@ class SearchFragment :
|
|||
is Artist -> SearchFragmentDirections.actionShowArtist(item.uid)
|
||||
is Genre -> SearchFragmentDirections.actionShowGenre(item.uid)
|
||||
else -> return
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
imm.hide()
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import androidx.annotation.IdRes
|
|||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.text.Normalizer
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -42,6 +41,7 @@ import org.oxycblt.auxio.ui.recycler.Item
|
|||
import org.oxycblt.auxio.util.TaskGuard
|
||||
import org.oxycblt.auxio.util.application
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import java.text.Normalizer
|
||||
|
||||
/**
|
||||
* The [ViewModel] for search functionality.
|
||||
|
@ -157,12 +157,12 @@ class SearchViewModel(application: Application) :
|
|||
|
||||
private inline fun <T : Music> List<T>.baseFilterBy(value: String, additional: (T) -> Boolean) =
|
||||
filter {
|
||||
// The basic comparison is first by the *normalized* name, as that allows a
|
||||
// non-unicode search to match with some unicode characters. If that fails,
|
||||
// filter impls have fallback values, primarily around sort tags or file names.
|
||||
it.resolveNameNormalized(application).contains(value, ignoreCase = true) ||
|
||||
additional(it)
|
||||
}
|
||||
// The basic comparison is first by the *normalized* name, as that allows a
|
||||
// non-unicode search to match with some unicode characters. If that fails,
|
||||
// filter impls have fallback values, primarily around sort tags or file names.
|
||||
it.resolveNameNormalized(application).contains(value, ignoreCase = true) ||
|
||||
additional(it)
|
||||
}
|
||||
.ifEmpty { null }
|
||||
|
||||
private fun Music.resolveNameNormalized(context: Context): String {
|
||||
|
|
|
@ -84,7 +84,8 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
|||
binding.aboutTotalDuration.text =
|
||||
getString(
|
||||
R.string.fmt_lib_total_duration,
|
||||
songs.sumOf { it.durationMs }.formatDurationMs(false))
|
||||
songs.sumOf { it.durationMs }.formatDurationMs(false)
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateAlbumCount(albums: List<Album>) {
|
||||
|
|
|
@ -77,7 +77,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
get() =
|
||||
inner.getInt(
|
||||
context.getString(R.string.set_key_theme),
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
)
|
||||
|
||||
/** Whether the dark theme should be black or not */
|
||||
val useBlackTheme: Boolean
|
||||
|
@ -97,7 +98,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var libTabs: Array<Tab>
|
||||
get() =
|
||||
Tab.fromSequence(
|
||||
inner.getInt(context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT))
|
||||
inner.getInt(context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT)
|
||||
)
|
||||
?: unlikelyToBeNull(Tab.fromSequence(Tab.SEQUENCE_DEFAULT))
|
||||
set(value) {
|
||||
inner.edit {
|
||||
|
@ -122,7 +124,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
val barAction: BarAction
|
||||
get() =
|
||||
BarAction.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_bar_action), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_bar_action), Int.MIN_VALUE)
|
||||
)
|
||||
?: BarAction.NEXT
|
||||
|
||||
/**
|
||||
|
@ -140,7 +143,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
val replayGainMode: ReplayGainMode
|
||||
get() =
|
||||
ReplayGainMode.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE)
|
||||
)
|
||||
?: ReplayGainMode.DYNAMIC
|
||||
|
||||
/** The current ReplayGain pre-amp configuration */
|
||||
|
@ -148,7 +152,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
get() =
|
||||
ReplayGainPreAmp(
|
||||
inner.getFloat(context.getString(R.string.set_key_pre_amp_with), 0f),
|
||||
inner.getFloat(context.getString(R.string.set_key_pre_amp_without), 0f))
|
||||
inner.getFloat(context.getString(R.string.set_key_pre_amp_without), 0f)
|
||||
)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
putFloat(context.getString(R.string.set_key_pre_amp_with), value.with)
|
||||
|
@ -162,7 +167,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
get() =
|
||||
PlaybackMode.fromInt(
|
||||
inner.getInt(
|
||||
context.getString(R.string.set_key_library_song_playback_mode), Int.MIN_VALUE))
|
||||
context.getString(R.string.set_key_library_song_playback_mode),
|
||||
Int.MIN_VALUE
|
||||
)
|
||||
)
|
||||
?: PlaybackMode.ALL_SONGS
|
||||
|
||||
/**
|
||||
|
@ -173,7 +181,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
get() =
|
||||
PlaybackMode.fromInt(
|
||||
inner.getInt(
|
||||
context.getString(R.string.set_key_detail_song_playback_mode), Int.MIN_VALUE))
|
||||
context.getString(R.string.set_key_detail_song_playback_mode),
|
||||
Int.MIN_VALUE
|
||||
)
|
||||
)
|
||||
|
||||
/** Whether shuffle should stay on when a new song is selected. */
|
||||
val keepShuffle: Boolean
|
||||
|
@ -201,7 +212,9 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
.mapNotNull { Directory.fromDocumentUri(storageManager, it) }
|
||||
|
||||
return MusicDirs(
|
||||
dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false))
|
||||
dirs,
|
||||
inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false)
|
||||
)
|
||||
}
|
||||
|
||||
/** Set the list of directories that music should be hidden/loaded from. */
|
||||
|
@ -209,9 +222,12 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
inner.edit {
|
||||
putStringSet(
|
||||
context.getString(R.string.set_key_music_dirs),
|
||||
musicDirs.dirs.map(Directory::toDocumentUri).toSet())
|
||||
musicDirs.dirs.map(Directory::toDocumentUri).toSet()
|
||||
)
|
||||
putBoolean(
|
||||
context.getString(R.string.set_key_music_dirs_include), musicDirs.shouldInclude)
|
||||
context.getString(R.string.set_key_music_dirs_include),
|
||||
musicDirs.shouldInclude
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
@ -233,12 +249,14 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var searchFilterMode: DisplayMode?
|
||||
get() =
|
||||
DisplayMode.fromInt(
|
||||
inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE)
|
||||
)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
putInt(
|
||||
context.getString(R.string.set_key_search_filter),
|
||||
value?.intCode ?: Int.MIN_VALUE)
|
||||
value?.intCode ?: Int.MIN_VALUE
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +265,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var libSongSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE)
|
||||
)
|
||||
?: Sort(Sort.Mode.ByName, true)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
|
@ -260,7 +279,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var libAlbumSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE)
|
||||
)
|
||||
?: Sort(Sort.Mode.ByName, true)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
|
@ -273,7 +293,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var libArtistSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE)
|
||||
)
|
||||
?: Sort(Sort.Mode.ByName, true)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
|
@ -286,7 +307,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var libGenreSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE)
|
||||
)
|
||||
?: Sort(Sort.Mode.ByName, true)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
|
@ -301,7 +323,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var sort =
|
||||
Sort.fromIntCode(
|
||||
inner.getInt(
|
||||
context.getString(R.string.set_key_detail_album_sort), Int.MIN_VALUE))
|
||||
context.getString(R.string.set_key_detail_album_sort),
|
||||
Int.MIN_VALUE
|
||||
)
|
||||
)
|
||||
?: Sort(Sort.Mode.ByDisc, true)
|
||||
|
||||
// Correct legacy album sort modes to Disc
|
||||
|
@ -322,7 +347,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var detailArtistSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE)
|
||||
)
|
||||
?: Sort(Sort.Mode.ByYear, false)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
|
@ -335,7 +361,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
|||
var detailGenreSort: Sort
|
||||
get() =
|
||||
Sort.fromIntCode(
|
||||
inner.getInt(context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE))
|
||||
inner.getInt(context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE)
|
||||
)
|
||||
?: Sort(Sort.Mode.ByName, true)
|
||||
set(value) {
|
||||
inner.edit {
|
||||
|
|
|
@ -26,10 +26,10 @@ import androidx.core.content.res.getTextArrayOrThrow
|
|||
import androidx.preference.DialogPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import java.lang.reflect.Field
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.lazyReflectedField
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import java.lang.reflect.Field
|
||||
|
||||
class IntListPreference
|
||||
@JvmOverloads
|
||||
|
@ -57,13 +57,18 @@ constructor(
|
|||
init {
|
||||
val prefAttrs =
|
||||
context.obtainStyledAttributes(
|
||||
attrs, R.styleable.IntListPreference, defStyleAttr, defStyleRes)
|
||||
attrs,
|
||||
R.styleable.IntListPreference,
|
||||
defStyleAttr,
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
entries = prefAttrs.getTextArrayOrThrow(R.styleable.IntListPreference_entries)
|
||||
|
||||
values =
|
||||
context.resources.getIntArray(
|
||||
prefAttrs.getResourceIdOrThrow(R.styleable.IntListPreference_entryValues))
|
||||
prefAttrs.getResourceIdOrThrow(R.styleable.IntListPreference_entryValues)
|
||||
)
|
||||
|
||||
val offValueId = prefAttrs.getResourceId(R.styleable.IntListPreference_offValue, -1)
|
||||
if (offValueId > -1) {
|
||||
|
@ -147,6 +152,6 @@ constructor(
|
|||
|
||||
companion object {
|
||||
private val PREFERENCE_DEFAULT_VALUE_FIELD: Field by
|
||||
lazyReflectedField(Preference::class, "mDefaultValue")
|
||||
lazyReflectedField(Preference::class, "mDefaultValue")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,14 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
if (child != null) {
|
||||
val coordinator = parent as CoordinatorLayout
|
||||
coordinatorLayoutBehavior?.onNestedPreScroll(
|
||||
coordinator, this, coordinator, 0, 0, tConsumed, 0)
|
||||
coordinator,
|
||||
this,
|
||||
coordinator,
|
||||
0,
|
||||
0,
|
||||
tConsumed,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
true
|
||||
|
|
|
@ -23,10 +23,10 @@ import android.view.View
|
|||
import android.view.WindowInsets
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
|
||||
import kotlin.math.abs
|
||||
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
|
||||
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* A behavior that automatically re-layouts and re-insets content to align with the parent layout's
|
||||
|
@ -127,7 +127,11 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
|
|||
val bars = insets.systemBarInsetsCompat
|
||||
|
||||
insets.replaceSystemBarInsetsCompat(
|
||||
bars.left, bars.top, bars.right, (bars.bottom - consumed).coerceAtLeast(0))
|
||||
bars.left,
|
||||
bars.top,
|
||||
bars.right,
|
||||
(bars.bottom - consumed).coerceAtLeast(0)
|
||||
)
|
||||
}
|
||||
|
||||
setup = true
|
||||
|
@ -141,7 +145,9 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
|
|||
View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.EXACTLY)
|
||||
val contentHeightSpec =
|
||||
View.MeasureSpec.makeMeasureSpec(
|
||||
parent.measuredHeight - consumed, View.MeasureSpec.EXACTLY)
|
||||
parent.measuredHeight - consumed,
|
||||
View.MeasureSpec.EXACTLY
|
||||
)
|
||||
|
||||
child.measure(contentWidthSpec, contentHeightSpec)
|
||||
}
|
||||
|
|
|
@ -146,7 +146,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
compareByDynamic(ascending, BasicComparator.ALBUM) { it.album },
|
||||
compareBy(NullableComparator.INT) { it.disc },
|
||||
compareBy(NullableComparator.INT) { it.track },
|
||||
compareBy(BasicComparator.SONG))
|
||||
compareBy(BasicComparator.SONG)
|
||||
)
|
||||
}
|
||||
|
||||
/** Sort by the artist of an item, only supported by [Album] and [Song] */
|
||||
|
@ -164,13 +165,15 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
compareByDescending(BasicComparator.ALBUM) { it.album },
|
||||
compareBy(NullableComparator.INT) { it.disc },
|
||||
compareBy(NullableComparator.INT) { it.track },
|
||||
compareBy(BasicComparator.SONG))
|
||||
compareBy(BasicComparator.SONG)
|
||||
)
|
||||
|
||||
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending, BasicComparator.ARTIST) { it.artist },
|
||||
compareByDescending(NullableComparator.DATE) { it.date },
|
||||
compareBy(BasicComparator.ALBUM))
|
||||
compareBy(BasicComparator.ALBUM)
|
||||
)
|
||||
}
|
||||
|
||||
/** Sort by the year of an item, only supported by [Album] and [Song] */
|
||||
|
@ -187,12 +190,14 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
compareByDescending(BasicComparator.ALBUM) { it.album },
|
||||
compareBy(NullableComparator.INT) { it.disc },
|
||||
compareBy(NullableComparator.INT) { it.track },
|
||||
compareBy(BasicComparator.SONG))
|
||||
compareBy(BasicComparator.SONG)
|
||||
)
|
||||
|
||||
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending, NullableComparator.DATE) { it.date },
|
||||
compareBy(BasicComparator.ALBUM))
|
||||
compareBy(BasicComparator.ALBUM)
|
||||
)
|
||||
}
|
||||
|
||||
/** Sort by the duration of the item. Supports all items. */
|
||||
|
@ -205,20 +210,27 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
|
||||
override fun getSongComparator(ascending: Boolean): Comparator<Song> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.SONG))
|
||||
compareByDynamic(ascending) { it.durationMs },
|
||||
compareBy(BasicComparator.SONG)
|
||||
)
|
||||
|
||||
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.ALBUM))
|
||||
compareByDynamic(ascending) { it.durationMs },
|
||||
compareBy(BasicComparator.ALBUM)
|
||||
)
|
||||
|
||||
override fun getArtistComparator(ascending: Boolean): Comparator<Artist> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.durationMs },
|
||||
compareBy(BasicComparator.ARTIST))
|
||||
compareBy(BasicComparator.ARTIST)
|
||||
)
|
||||
|
||||
override fun getGenreComparator(ascending: Boolean): Comparator<Genre> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.GENRE))
|
||||
compareByDynamic(ascending) { it.durationMs },
|
||||
compareBy(BasicComparator.GENRE)
|
||||
)
|
||||
}
|
||||
|
||||
/** Sort by the amount of songs. Only applicable to music parents. */
|
||||
|
@ -231,16 +243,21 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
|
||||
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.ALBUM))
|
||||
compareByDynamic(ascending) { it.songs.size },
|
||||
compareBy(BasicComparator.ALBUM)
|
||||
)
|
||||
|
||||
override fun getArtistComparator(ascending: Boolean): Comparator<Artist> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.songs.size },
|
||||
compareBy(BasicComparator.ARTIST))
|
||||
compareBy(BasicComparator.ARTIST)
|
||||
)
|
||||
|
||||
override fun getGenreComparator(ascending: Boolean): Comparator<Genre> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.GENRE))
|
||||
compareByDynamic(ascending) { it.songs.size },
|
||||
compareBy(BasicComparator.GENRE)
|
||||
)
|
||||
}
|
||||
|
||||
/** Sort by the disc, and then track number of an item. Only supported by [Song]. */
|
||||
|
@ -255,7 +272,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
MultiComparator(
|
||||
compareByDynamic(ascending, NullableComparator.INT) { it.disc },
|
||||
compareBy(NullableComparator.INT) { it.track },
|
||||
compareBy(BasicComparator.SONG))
|
||||
compareBy(BasicComparator.SONG)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -273,7 +291,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
MultiComparator(
|
||||
compareBy(NullableComparator.INT) { it.disc },
|
||||
compareByDynamic(ascending, NullableComparator.INT) { it.track },
|
||||
compareBy(BasicComparator.SONG))
|
||||
compareBy(BasicComparator.SONG)
|
||||
)
|
||||
}
|
||||
|
||||
/** Sort by the time the item was added. Only supported by [Song] */
|
||||
|
@ -286,12 +305,15 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
|
|||
|
||||
override fun getSongComparator(ascending: Boolean): Comparator<Song> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { it.dateAdded }, compareBy(BasicComparator.SONG))
|
||||
compareByDynamic(ascending) { it.dateAdded },
|
||||
compareBy(BasicComparator.SONG)
|
||||
)
|
||||
|
||||
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
|
||||
MultiComparator(
|
||||
compareByDynamic(ascending) { album -> album.songs.minOf { it.dateAdded } },
|
||||
compareBy(BasicComparator.ALBUM))
|
||||
compareBy(BasicComparator.ALBUM)
|
||||
)
|
||||
}
|
||||
|
||||
protected inline fun <T : Music, K> compareByDynamic(
|
||||
|
|
|
@ -39,7 +39,8 @@ private val ACCENT_NAMES =
|
|||
R.string.clr_orange,
|
||||
R.string.clr_brown,
|
||||
R.string.clr_grey,
|
||||
R.string.clr_dynamic)
|
||||
R.string.clr_dynamic
|
||||
)
|
||||
|
||||
private val ACCENT_THEMES =
|
||||
intArrayOf(
|
||||
|
@ -60,7 +61,7 @@ private val ACCENT_THEMES =
|
|||
R.style.Theme_Auxio_Brown,
|
||||
R.style.Theme_Auxio_Grey,
|
||||
R.style.Theme_Auxio_App // Dynamic colors are on the base theme
|
||||
)
|
||||
)
|
||||
|
||||
private val ACCENT_BLACK_THEMES =
|
||||
intArrayOf(
|
||||
|
@ -81,7 +82,7 @@ private val ACCENT_BLACK_THEMES =
|
|||
R.style.Theme_Auxio_Black_Brown,
|
||||
R.style.Theme_Auxio_Black_Grey,
|
||||
R.style.Theme_Auxio_Black // Dynamic colors are on the base theme
|
||||
)
|
||||
)
|
||||
|
||||
private val ACCENT_PRIMARY_COLORS =
|
||||
intArrayOf(
|
||||
|
@ -101,7 +102,8 @@ private val ACCENT_PRIMARY_COLORS =
|
|||
R.color.orange_primary,
|
||||
R.color.brown_primary,
|
||||
R.color.grey_primary,
|
||||
R.color.dynamic_primary)
|
||||
R.color.dynamic_primary
|
||||
)
|
||||
|
||||
/**
|
||||
* The data object for an accent. In the UI this is known as a "Color Scheme." This can be nominally
|
||||
|
|
|
@ -62,7 +62,8 @@ class AccentCustomizeDialog :
|
|||
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
||||
} else {
|
||||
settings.accent
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
|
|
@ -21,9 +21,9 @@ import android.content.Context
|
|||
import android.util.AttributeSet
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.max
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.getDimenSize
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width
|
||||
|
|
|
@ -165,7 +165,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
|
|||
centerY + radius,
|
||||
startAngle,
|
||||
sweepAngle,
|
||||
false)
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ import androidx.core.view.isInvisible
|
|||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.abs
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
|
||||
import org.oxycblt.auxio.util.getDimenSize
|
||||
|
@ -41,6 +40,7 @@ import org.oxycblt.auxio.util.getDrawableCompat
|
|||
import org.oxycblt.auxio.util.isRtl
|
||||
import org.oxycblt.auxio.util.isUnder
|
||||
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* A [RecyclerView] that enables better fast-scrolling. This is fundamentally a implementation of
|
||||
|
@ -97,7 +97,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
FastScrollPopupView(context).apply {
|
||||
layoutParams =
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
.apply {
|
||||
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
|
||||
marginEnd = context.getDimenSize(R.dimen.spacing_small)
|
||||
|
@ -172,7 +174,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: State) {
|
||||
onPreDraw()
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// We use a listener instead of overriding onTouchEvent so that we don't conflict with
|
||||
// RecyclerView touch events.
|
||||
|
@ -188,7 +191,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
): Boolean {
|
||||
return onItemTouch(event)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// --- RECYCLERVIEW EVENT MANAGEMENT ---
|
||||
|
@ -243,7 +247,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
thumbWidth +
|
||||
popupLayoutParams.leftMargin +
|
||||
popupLayoutParams.rightMargin,
|
||||
popupLayoutParams.width)
|
||||
popupLayoutParams.width
|
||||
)
|
||||
|
||||
val heightMeasureSpec =
|
||||
ViewGroup.getChildMeasureSpec(
|
||||
|
@ -252,7 +257,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
thumbPadding.bottom +
|
||||
popupLayoutParams.topMargin +
|
||||
popupLayoutParams.bottomMargin,
|
||||
popupLayoutParams.height)
|
||||
popupLayoutParams.height
|
||||
)
|
||||
|
||||
popupView.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
|
@ -273,7 +279,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
(thumbTop + thumbAnchorY - popupAnchorY)
|
||||
.coerceAtLeast(thumbPadding.top + popupLayoutParams.topMargin)
|
||||
.coerceAtMost(
|
||||
height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
|
||||
height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight
|
||||
)
|
||||
|
||||
popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight)
|
||||
}
|
||||
|
@ -348,7 +355,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
MotionEvent.ACTION_MOVE -> {
|
||||
if (!dragging &&
|
||||
thumbView.isUnder(downX, thumbView.top.toFloat(), minTouchTargetSize) &&
|
||||
abs(eventY - downY) > touchSlop) {
|
||||
abs(eventY - downY) > touchSlop
|
||||
) {
|
||||
if (thumbView.isUnder(downX, downY, minTouchTargetSize)) {
|
||||
dragStartY = lastY
|
||||
dragStartThumbOffset = thumbOffset
|
||||
|
|
|
@ -26,10 +26,10 @@ import androidx.fragment.app.DialogFragment
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* A dialog fragment enabling ViewBinding inflation and usage across the dialog fragment lifecycle.
|
||||
|
|
|
@ -23,10 +23,10 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* A fragment enabling ViewBinding inflation and usage across the fragment lifecycle.
|
||||
|
|
|
@ -106,6 +106,8 @@ abstract class DialogViewHolder(root: View) : RecyclerView.ViewHolder(root) {
|
|||
// Actually make the item full-width, which it won't be in dialogs
|
||||
root.layoutParams =
|
||||
RecyclerView.LayoutParams(
|
||||
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||
RecyclerView.LayoutParams.MATCH_PARENT,
|
||||
RecyclerView.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
initialPadding.left,
|
||||
initialPadding.top,
|
||||
initialPadding.right,
|
||||
initialPadding.bottom + insets.systemBarInsetsCompat.bottom)
|
||||
initialPadding.bottom + insets.systemBarInsetsCompat.bottom
|
||||
)
|
||||
|
||||
return insets
|
||||
}
|
||||
|
|
|
@ -126,7 +126,8 @@ class SyncListDiffer<T>(
|
|||
throw AssertionError()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
field = newList
|
||||
result.dispatchUpdatesTo(updateCallback)
|
||||
|
|
|
@ -68,13 +68,15 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
ViewGroup.getChildMeasureSpec(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
0,
|
||||
divider.layoutParams.width)
|
||||
divider.layoutParams.width
|
||||
)
|
||||
|
||||
val heightMeasureSpec =
|
||||
ViewGroup.getChildMeasureSpec(
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
|
||||
0,
|
||||
divider.layoutParams.height)
|
||||
divider.layoutParams.height
|
||||
)
|
||||
|
||||
divider.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
|
|
|
@ -122,7 +122,8 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
|
|||
binding.context.getString(
|
||||
R.string.fmt_two,
|
||||
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size),
|
||||
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
|
||||
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)
|
||||
)
|
||||
// binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) }
|
||||
binding.root.setOnLongClickListener {
|
||||
listener.onOpenMenu(item, it)
|
||||
|
|
|
@ -37,9 +37,9 @@ import androidx.annotation.PluralsRes
|
|||
import androidx.annotation.Px
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlin.reflect.KClass
|
||||
import org.oxycblt.auxio.IntegerTable
|
||||
import org.oxycblt.auxio.MainActivity
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/** Shortcut to get a [LayoutInflater] from a [Context] */
|
||||
val Context.inflater: LayoutInflater
|
||||
|
@ -152,7 +152,8 @@ fun Context.newMainPendingIntent(): PendingIntent =
|
|||
this,
|
||||
IntegerTable.REQUEST_CODE,
|
||||
Intent(this, MainActivity::class.java),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
|
||||
)
|
||||
|
||||
/** Create a broadcast [PendingIntent] */
|
||||
fun Context.newBroadcastPendingIntent(what: String): PendingIntent =
|
||||
|
@ -160,4 +161,5 @@ fun Context.newBroadcastPendingIntent(what: String): PendingIntent =
|
|||
this,
|
||||
IntegerTable.REQUEST_CODE,
|
||||
Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
|
||||
)
|
||||
|
|
|
@ -231,7 +231,8 @@ val WindowInsets.systemGestureInsetsCompat: Insets
|
|||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
Insets.max(
|
||||
getCompatInsets(WindowInsets.Type.systemGestures()),
|
||||
getCompatInsets(WindowInsets.Type.systemBars()))
|
||||
getCompatInsets(WindowInsets.Type.systemBars())
|
||||
)
|
||||
}
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
|
||||
@Suppress("DEPRECATION")
|
||||
|
@ -246,7 +247,8 @@ fun WindowInsets.getSystemWindowCompatInsets() =
|
|||
systemWindowInsetLeft,
|
||||
systemWindowInsetTop,
|
||||
systemWindowInsetRight,
|
||||
systemWindowInsetBottom)
|
||||
systemWindowInsetBottom
|
||||
)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
|
@ -270,7 +272,8 @@ fun WindowInsets.replaceSystemBarInsetsCompat(
|
|||
WindowInsets.Builder(this)
|
||||
.setInsets(
|
||||
WindowInsets.Type.systemBars(),
|
||||
Insets.of(left, top, right, bottom).toPlatformInsets())
|
||||
Insets.of(left, top, right, bottom).toPlatformInsets()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
else -> {
|
||||
|
|
|
@ -82,11 +82,13 @@ private val Any.autoTag: String
|
|||
@Suppress("KotlinConstantConditions")
|
||||
private fun basedCopyleftNotice(): Boolean {
|
||||
if (BuildConfig.APPLICATION_ID != "org.oxycblt.auxio" &&
|
||||
BuildConfig.APPLICATION_ID != "org.oxycblt.auxio.debug") {
|
||||
BuildConfig.APPLICATION_ID != "org.oxycblt.auxio.debug"
|
||||
) {
|
||||
Log.d(
|
||||
"Auxio Project",
|
||||
"Friendly reminder: Auxio is licensed under the " +
|
||||
"GPLv3 and all derivative apps must be made open source!")
|
||||
"GPLv3 and all derivative apps must be made open source!"
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
package org.oxycblt.auxio.util
|
||||
|
||||
import android.os.Looper
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.CancellationException
|
||||
import kotlin.reflect.KClass
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
|
||||
/** Assert that we are on a background thread. */
|
||||
fun requireBackgroundThread() {
|
||||
|
|
|
@ -126,7 +126,8 @@ private fun RemoteViews.applyCover(
|
|||
setImageViewBitmap(R.id.widget_cover, state.cover)
|
||||
setContentDescription(
|
||||
R.id.widget_cover,
|
||||
context.getString(R.string.desc_album_cover, state.song.album.resolveName(context)))
|
||||
context.getString(R.string.desc_album_cover, state.song.album.resolveName(context))
|
||||
)
|
||||
} else {
|
||||
setImageViewResource(R.id.widget_cover, R.drawable.ic_remote_default_cover_24)
|
||||
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
|
||||
|
@ -144,7 +145,8 @@ private fun RemoteViews.applyPlayPauseControls(
|
|||
|
||||
setOnClickPendingIntent(
|
||||
R.id.widget_play_pause,
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE))
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE)
|
||||
)
|
||||
|
||||
// Like the Android 13 media controls, use a circular fab when paused, and a squircle fab
|
||||
// when playing.
|
||||
|
@ -173,10 +175,14 @@ private fun RemoteViews.applyBasicControls(
|
|||
applyPlayPauseControls(context, state)
|
||||
|
||||
setOnClickPendingIntent(
|
||||
R.id.widget_skip_prev, context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_PREV))
|
||||
R.id.widget_skip_prev,
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_PREV)
|
||||
)
|
||||
|
||||
setOnClickPendingIntent(
|
||||
R.id.widget_skip_next, context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_NEXT))
|
||||
R.id.widget_skip_next,
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_NEXT)
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -189,11 +195,13 @@ private fun RemoteViews.applyFullControls(
|
|||
|
||||
setOnClickPendingIntent(
|
||||
R.id.widget_repeat,
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_INC_REPEAT_MODE))
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_INC_REPEAT_MODE)
|
||||
)
|
||||
|
||||
setOnClickPendingIntent(
|
||||
R.id.widget_shuffle,
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_INVERT_SHUFFLE))
|
||||
context.newBroadcastPendingIntent(PlaybackService.ACTION_INVERT_SHUFFLE)
|
||||
)
|
||||
|
||||
val shuffleRes =
|
||||
when {
|
||||
|
|
|
@ -22,7 +22,6 @@ import android.graphics.Bitmap
|
|||
import android.os.Build
|
||||
import coil.request.ImageRequest
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import kotlin.math.sqrt
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.image.BitmapProvider
|
||||
import org.oxycblt.auxio.image.SquareFrameTransform
|
||||
|
@ -34,6 +33,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode
|
|||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.getDimenSize
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the
|
||||
|
@ -109,7 +109,8 @@ class WidgetComponent(private val context: Context) :
|
|||
.size(computeSize(sw, sh, 10f))
|
||||
.transformations(
|
||||
SquareFrameTransform.INSTANCE,
|
||||
RoundedCornersTransformation(cornerRadius.toFloat()))
|
||||
RoundedCornersTransformation(cornerRadius.toFloat())
|
||||
)
|
||||
} else {
|
||||
// Divide by two to really make sure we aren't hitting the memory limit.
|
||||
builder.size(computeSize(sw, sh, 2f))
|
||||
|
@ -120,7 +121,8 @@ class WidgetComponent(private val context: Context) :
|
|||
val state = WidgetState(song, bitmap, isPlaying, repeatMode, isShuffled)
|
||||
widget.update(context, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun computeSize(sw: Int, sh: Int, modifier: Float) =
|
||||
|
@ -146,7 +148,8 @@ class WidgetComponent(private val context: Context) :
|
|||
override fun onSettingChanged(key: String) {
|
||||
if (key == context.getString(R.string.set_key_show_covers) ||
|
||||
key == context.getString(R.string.set_key_quality_covers) ||
|
||||
key == context.getString(R.string.set_key_round_mode)) {
|
||||
key == context.getString(R.string.set_key_round_mode)
|
||||
) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,8 @@ class WidgetProvider : AppWidgetProvider() {
|
|||
SizeF(180f, 152f) to createSmallWidget(context, state),
|
||||
SizeF(272f, 152f) to createWideWidget(context, state),
|
||||
SizeF(180f, 272f) to createMediumWidget(context, state),
|
||||
SizeF(272f, 272f) to createLargeWidget(context, state))
|
||||
SizeF(272f, 272f) to createLargeWidget(context, state)
|
||||
)
|
||||
|
||||
val awm = AppWidgetManager.getInstance(context)
|
||||
|
||||
|
@ -70,7 +71,9 @@ class WidgetProvider : AppWidgetProvider() {
|
|||
} catch (e: Exception) {
|
||||
logW("Unable to update widget: $e")
|
||||
awm.updateAppWidget(
|
||||
ComponentName(context, this::class.java), createDefaultWidget(context))
|
||||
ComponentName(context, this::class.java),
|
||||
createDefaultWidget(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -204,14 +204,14 @@
|
|||
<string name="set_rewind_prev_desc">Rewind before skipping to the previous song</string>
|
||||
<string name="set_repeat_pause">Pause on repeat</string>
|
||||
<string name="set_repeat_pause_desc">Pause when a song repeats</string>
|
||||
|
||||
<string name="set_content">Content</string>
|
||||
<string name="set_save_state">Save playback state</string>
|
||||
<string name="set_save_desc">Save the current playback state now</string>
|
||||
<string name="set_wipe_state">Clear playback state</string>
|
||||
<string name="set_wipe_desc">Clear the previously saved playback state (if any)</string>
|
||||
<string name="set_restore_state">Restore playback state</string>
|
||||
<string name="set_restore_desc">Restore the previously saved playback state (if any)</string>
|
||||
|
||||
<string name="set_content">Content</string>
|
||||
<string name="set_reindex">Reload music</string>
|
||||
<string name="set_reindex_desc">May wipe playback state</string>
|
||||
<string name="set_dirs">Music folders</string>
|
||||
|
|
Loading…
Reference in a new issue