app: cleanup

This commit is contained in:
Alexander Capehart 2025-03-17 08:12:39 -06:00
parent 343856ac69
commit aac6d8ef4d
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
38 changed files with 76 additions and 228 deletions

View file

@ -18,8 +18,8 @@ android {
defaultConfig {
applicationId namespace
versionName "4.0.2"
versionCode 61
versionName "4.0.3"
versionCode 62
minSdk min_sdk
targetSdk target_sdk

View file

@ -1309,7 +1309,6 @@ public class BackportBottomSheetBehavior<V extends View> extends CoordinatorLayo
+ " should not be set externally.");
}
if (!hideable && state == STATE_HIDDEN) {
Log.w(TAG, "Cannot set state: " + state);
return;
}
final int finalState;

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio
import android.animation.ValueAnimator
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewTreeObserver
@ -514,8 +513,6 @@ class MainFragment :
}
}
private var scrimAnimator: ValueAnimator? = null
private fun updateSpeedDial(open: Boolean) {
requireNotNull(speedDialBackCallback) { "SpeedDialBackPressedCallback was not available" }
.invalidateEnabled(open)

View file

@ -98,7 +98,7 @@ sealed interface ArtistShowChoices {
val uid: Music.UID
/** The current [Artist] choices. */
val choices: List<Artist>
/** Sanitize this instance with a [DeviceLibrary]. */
/** Sanitize this instance with a [Library]. */
fun sanitize(newLibrary: Library): ArtistShowChoices?
/** Backing implementation of [ArtistShowChoices] that is based on a [Song]. */

View file

@ -37,12 +37,10 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.transition.MaterialSharedAxis
import dagger.hilt.android.AndroidEntryPoint
import java.lang.reflect.Field
import java.lang.reflect.Method
import kotlin.math.abs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeBinding
@ -68,7 +66,6 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.lazyReflectedMethod
import org.oxycblt.auxio.util.navigateSafe
import org.oxycblt.auxio.util.showToast
import org.oxycblt.musikr.IndexingProgress
@ -94,7 +91,6 @@ class HomeFragment :
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null
private var getContentLauncher: ActivityResultLauncher<String>? = null
private var pendingImportTarget: Playlist? = null
private var lastUpdateTime = -1L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -512,11 +508,5 @@ class HomeFragment :
private companion object {
val VP_RECYCLER_FIELD: Field by lazyReflectedField(ViewPager2::class, "mRecyclerView")
val RV_TOUCH_SLOP_FIELD: Field by lazyReflectedField(RecyclerView::class, "mTouchSlop")
val FAB_HIDE_FROM_USER_FIELD: Method by
lazyReflectedMethod(
FloatingActionButton::class,
"hide",
FloatingActionButton.OnVisibilityChangedListener::class,
Boolean::class)
}
}

View file

@ -30,7 +30,7 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.image.covers.SettingCovers
import org.oxycblt.musikr.covers.CoverResult
class CoverProvider() : ContentProvider() {
class CoverProvider : ContentProvider() {
override fun onCreate(): Boolean = true
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {

View file

@ -37,6 +37,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.Px
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.children
import androidx.core.view.isEmpty
import androidx.core.view.updateMarginsRelative
import androidx.core.widget.ImageViewCompat
import coil3.ImageLoader
@ -172,7 +173,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
super.onFinishInflate()
// The image isn't added if other children have populated the body. This is by design.
if (childCount == 0) {
if (isEmpty()) {
addView(image)
}

View file

@ -19,9 +19,9 @@
package org.oxycblt.auxio.image.coil
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.toDrawable
import coil3.ImageLoader
import coil3.asImage
@ -90,8 +90,7 @@ private constructor(
val mosaicFrameSize =
Size(Dimension(mosaicSize.width / 2), Dimension(mosaicSize.height / 2))
val mosaicBitmap =
Bitmap.createBitmap(mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888)
val mosaicBitmap = createBitmap(mosaicSize.width, mosaicSize.height)
val canvas = Canvas(mosaicBitmap)
var x = 0

View file

@ -18,32 +18,20 @@
package org.oxycblt.auxio.image.coil
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import androidx.core.graphics.drawable.toDrawable
import coil3.ImageLoader
import coil3.asImage
import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.fetch.FetchResult
import coil3.fetch.Fetcher
import coil3.fetch.ImageFetchResult
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import coil3.size.Dimension
import coil3.size.Size
import coil3.size.pxOrElse
import java.io.InputStream
import javax.inject.Inject
import okio.FileSystem
import okio.buffer
import okio.source
import org.oxycblt.musikr.covers.Cover
class CoverFetcher private constructor(private val context: Context, private val cover: Cover) :
Fetcher {
class CoverFetcher private constructor(private val cover: Cover) : Fetcher {
override suspend fun fetch(): FetchResult? {
val stream = cover.open() ?: return null
return SourceFetchResult(
@ -52,59 +40,8 @@ class CoverFetcher private constructor(private val context: Context, private val
dataSource = DataSource.DISK)
}
/** Derived from phonograph: https://github.com/kabouzeid/Phonograph */
private suspend fun createMosaic(streams: List<InputStream>, size: Size): FetchResult {
// Use whatever size coil gives us to create the mosaic.
val mosaicSize = android.util.Size(size.width.mosaicSize(), size.height.mosaicSize())
val mosaicFrameSize =
Size(Dimension(mosaicSize.width / 2), Dimension(mosaicSize.height / 2))
val mosaicBitmap =
Bitmap.createBitmap(mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(mosaicBitmap)
var x = 0
var y = 0
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
// and place it on a corner of the canvas.
for (stream in streams) {
if (y == mosaicSize.height) {
break
}
// Crop the bitmap down to a square so it leaves no empty space
// TODO: Work around this
val bitmap =
SquareCropTransformation.INSTANCE.transform(
BitmapFactory.decodeStream(stream), mosaicFrameSize)
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
x += bitmap.width
if (x == mosaicSize.width) {
x = 0
y += bitmap.height
}
}
// It's way easier to map this into a drawable then try to serialize it into an
// BufferedSource. Just make sure we mark it as "sampled" so Coil doesn't try to
// load low-res mosaics into high-res ImageViews.
return ImageFetchResult(
image = mosaicBitmap.toDrawable(context.resources).asImage(),
isSampled = true,
dataSource = DataSource.DISK)
}
private fun Dimension.mosaicSize(): Int {
// Since we want the mosaic to be perfectly divisible into two, we need to round any
// odd image sizes upwards to prevent the mosaic creation from failing.
val size = pxOrElse { 512 }
return if (size.mod(2) > 0) size + 1 else size
}
class Factory @Inject constructor() : Fetcher.Factory<Cover> {
override fun create(data: Cover, options: Options, imageLoader: ImageLoader) =
CoverFetcher(options.context, data)
CoverFetcher(data)
}
}

View file

@ -38,8 +38,8 @@ import coil3.transform.Transformation
import kotlin.math.roundToInt
/**
* A vendoring of [coil.transform.RoundedCornersTransformation] that can handle non-1:1 aspect ratio
* images without cropping them.
* A vendoring of coil's RoundedCornersTransformation that can handle non-1:1 aspect ratio images
* without cropping them.
*
* @author Coil Team, Alexander Capehart (OxygenCobalt)
*/

View file

@ -19,6 +19,7 @@
package org.oxycblt.auxio.image.coil
import android.graphics.Bitmap
import androidx.core.graphics.scale
import coil3.size.Size
import coil3.size.pxOrElse
import coil3.transform.Transformation
@ -46,7 +47,7 @@ class SquareCropTransformation : Transformation() {
val desiredHeight = size.height.pxOrElse { dstSize }
if (dstSize != desiredWidth || dstSize != desiredHeight) {
// Image is not the desired size, upscale it.
return Bitmap.createScaledBitmap(dst, desiredWidth, desiredHeight, true)
return dst.scale(desiredWidth, desiredHeight)
}
return dst
}

View file

@ -34,6 +34,7 @@ import android.view.ViewGroup
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.annotation.AttrRes
import androidx.core.view.isEmpty
import androidx.core.view.isInvisible
import androidx.core.view.updatePaddingRelative
import androidx.core.widget.TextViewCompat
@ -91,7 +92,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
private var thumbAnimator: Animator? = null
private val thumbView =
context.inflater.inflate(R.layout.view_scroll_thumb, null).apply {
context.inflater.inflate(R.layout.view_scroll_thumb, this).apply {
thumbSlider.jumpOut(this)
}
private val thumbPadding = Rect(0, 0, 0, 0)
@ -339,7 +340,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
// [proportion of scroll position to scroll range] * [total thumb range]
// This is somewhat adapted from the androidx RecyclerView FastScroller implementation.
val offsetY = computeVerticalScrollOffset()
if (computeVerticalScrollRange() < height || childCount == 0) {
if (computeVerticalScrollRange() < height || isEmpty()) {
fastScrollingPossible = false
hideThumb()
hidePopup()

View file

@ -188,8 +188,8 @@ interface MusicRepository {
/**
* Flags indicating which kinds of music information changed.
*
* @param deviceLibrary Whether the current [DeviceLibrary] has changed.
* @param library Whether the current [Playlist]s have changed.
* @param deviceLibrary Whether the current songs/albums/artists/genres has changed.
* @param userLibrary Whether the current playlists have changed.
*/
data class Changes(val deviceLibrary: Boolean, val userLibrary: Boolean)
@ -244,7 +244,7 @@ constructor(
) : MusicRepository {
private val updateListeners = mutableListOf<MusicRepository.UpdateListener>()
private val indexingListeners = mutableListOf<MusicRepository.IndexingListener>()
@Volatile private var indexingWorker: MusicRepository.IndexingWorker? = null
@Volatile private var indexingWorker: IndexingWorker? = null
@Volatile override var library: MutableLibrary? = null
@Volatile private var previousCompletedState: IndexingState.Completed? = null
@ -283,7 +283,7 @@ constructor(
}
@Synchronized
override fun registerWorker(worker: MusicRepository.IndexingWorker) {
override fun registerWorker(worker: IndexingWorker) {
if (indexingWorker != null) {
L.w("Worker is already registered")
return
@ -293,7 +293,7 @@ constructor(
}
@Synchronized
override fun unregisterWorker(worker: MusicRepository.IndexingWorker) {
override fun unregisterWorker(worker: IndexingWorker) {
if (indexingWorker !== worker) {
L.w("Given worker did not match current worker")
return

View file

@ -27,15 +27,10 @@ import org.oxycblt.auxio.R
* @author Alexander Capehart (OxygenCobalt)
*/
enum class MusicType {
/** @see Song */
SONGS,
/** @see Album */
ALBUMS,
/** @see Artist */
ARTISTS,
/** @see Genre */
GENRES,
/** @see Playlist */
PLAYLISTS;
/**

View file

@ -25,6 +25,7 @@ import android.view.LayoutInflater
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.recyclerview.widget.ConcatAdapter
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -80,7 +81,7 @@ class MusicSourcesDialog :
val locations =
savedInstanceState?.getStringArrayList(KEY_PENDING_LOCATIONS)?.mapNotNull {
MusicLocation.existing(requireContext(), Uri.parse(it))
MusicLocation.existing(requireContext(), it.toUri())
} ?: musicSettings.musicLocations
locationAdapter.addAll(locations)

View file

@ -51,7 +51,7 @@ class NewLocationFooterAdapter(private val listener: Listener) :
}
/**
* A [RecyclerView.ViewHolder] that displays a "New Playlist" choice in [NewPlaylistFooterAdapter].
* A [RecyclerView.ViewHolder] that displays a "New Playlist" choice in [NewLocationFooterAdapter].
* Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt)

View file

@ -48,13 +48,6 @@ fun Long.dsToMs() = times(100)
*/
fun Long.dsToSecs() = floorDiv(10)
/**
* Convert seconds into milliseconds.
*
* @return A converted millisecond value.
*/
fun Long.secsToMs() = times(1000)
/**
* Convert a millisecond value into a string duration.
*

View file

@ -18,7 +18,9 @@
package org.oxycblt.auxio.playback.service
import androidx.annotation.OptIn
import androidx.media3.common.C
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.source.ShuffleOrder
/**
@ -28,6 +30,7 @@ import androidx.media3.exoplayer.source.ShuffleOrder
*
* @author media3 team, Alexander Capehart (OxygenCobalt)
*/
@OptIn(UnstableApi::class)
class BetterShuffleOrder(private val shuffled: IntArray) : ShuffleOrder {
private val indexInShuffled: IntArray = IntArray(shuffled.size)

View file

@ -22,11 +22,13 @@ import android.content.Context
import android.content.Intent
import android.media.audiofx.AudioEffect
import android.provider.OpenableColumns
import androidx.annotation.OptIn
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.decoder.ffmpeg.FfmpegAudioRenderer
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.RenderersFactory
@ -62,6 +64,7 @@ import org.oxycblt.musikr.MusicParent
import org.oxycblt.musikr.Song
import timber.log.Timber as L
@OptIn(UnstableApi::class)
class ExoPlaybackStateHolder(
private val context: Context,
private val player: ExoPlayer,

View file

@ -19,6 +19,8 @@
package org.oxycblt.auxio.playback.service
import android.content.Context
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.ContentDataSource
import androidx.media3.datasource.DataSource
import androidx.media3.exoplayer.source.MediaSource
@ -41,6 +43,7 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
@OptIn(UnstableApi::class)
class SystemModule {
@Provides
fun mediaSourceFactory(

View file

@ -20,9 +20,9 @@ package org.oxycblt.auxio.settings
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import androidx.core.net.toUri
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
@ -102,7 +102,7 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
}
private fun Context.sendEmail(recipient: String) {
val intent = Intent(Intent.ACTION_SENDTO).apply { data = Uri.parse("mailto:$recipient") }
val intent = Intent(Intent.ACTION_SENDTO).apply { data = "mailto:$recipient".toUri() }
startIntent(intent)
}

View file

@ -46,25 +46,25 @@ class AnimConfig(
companion object {
val STANDARD = MR.attr.motionEasingStandardInterpolator
val EMPHASIZED = MR.attr.motionEasingEmphasizedInterpolator
// val EMPHASIZED = MR.attr.motionEasingEmphasizedInterpolator
val EMPHASIZED_ACCELERATE = MR.attr.motionEasingEmphasizedAccelerateInterpolator
val EMPHASIZED_DECELERATE = MR.attr.motionEasingEmphasizedDecelerateInterpolator
val SHORT1 = MR.attr.motionDurationShort1 to 50
val SHORT2 = MR.attr.motionDurationShort2 to 100
// val SHORT2 = MR.attr.motionDurationShort2 to 100
val SHORT3 = MR.attr.motionDurationShort3 to 150
val SHORT4 = MR.attr.motionDurationShort4 to 200
// val SHORT4 = MR.attr.motionDurationShort4 to 200
val MEDIUM1 = MR.attr.motionDurationMedium1 to 250
val MEDIUM2 = MR.attr.motionDurationMedium2 to 300
val MEDIUM3 = MR.attr.motionDurationMedium3 to 350
val MEDIUM4 = MR.attr.motionDurationMedium4 to 400
val LONG1 = MR.attr.motionDurationLong1 to 450
val LONG2 = MR.attr.motionDurationLong2 to 500
val LONG3 = MR.attr.motionDurationLong3 to 550
val LONG4 = MR.attr.motionDurationLong4 to 600
val EXTRA_LONG1 = MR.attr.motionDurationExtraLong1 to 700
val EXTRA_LONG2 = MR.attr.motionDurationExtraLong2 to 800
val EXTRA_LONG3 = MR.attr.motionDurationExtraLong3 to 900
val EXTRA_LONG4 = MR.attr.motionDurationExtraLong4 to 1000
// val MEDIUM4 = MR.attr.motionDurationMedium4 to 400
// val LONG1 = MR.attr.motionDurationLong1 to 450
// val LONG2 = MR.attr.motionDurationLong2 to 500
// val LONG3 = MR.attr.motionDurationLong3 to 550
// val LONG4 = MR.attr.motionDurationLong4 to 600
// val EXTRA_LONG1 = MR.attr.motionDurationExtraLong1 to 700
// val EXTRA_LONG2 = MR.attr.motionDurationExtraLong2 to 800
// val EXTRA_LONG3 = MR.attr.motionDurationExtraLong3 to 900
// val EXTRA_LONG4 = MR.attr.motionDurationExtraLong4 to 1000
fun of(context: Context, @AttrRes interpolator: Int, duration: Pair<Int, Int>) =
AnimConfig(context, interpolator, duration.first, duration.second)
@ -122,7 +122,7 @@ private constructor(
}
}
fun jumpToFadeIn(view: View) {
private fun jumpToFadeIn(view: View) {
view.apply {
alpha = 1f
scaleX = 1.0f

View file

@ -24,7 +24,6 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.os.Build
import android.view.View
import android.view.WindowInsets
@ -36,7 +35,6 @@ import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ShareCompat
import androidx.core.graphics.Insets
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.net.toUri
import androidx.core.view.children
import androidx.navigation.NavController
@ -106,10 +104,6 @@ private fun isUnderImpl(
val View.isRtl: Boolean
get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
/** Whether this [Drawable] is using an RTL layout direction. */
val Drawable.isRtl: Boolean
get() = DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL
/** Get a [Context] from a [ViewBinding]'s root [View]. */
val ViewBinding.context: Context
get() = root.context
@ -357,7 +351,7 @@ fun Context.startIntent(intent: Intent) {
// No app installed to open the link
showToast(R.string.err_no_app)
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
} else {
// On older versions of android, opening links from an ACTION_VIEW intent might
// not work in all cases, especially when no default app was set. If that is the
// case, we will try to manually handle these cases before we try to launch the

View file

@ -22,18 +22,11 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import java.util.concurrent.TimeoutException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import org.oxycblt.auxio.BuildConfig
import timber.log.Timber as L
/**
* A wrapper around [StateFlow] exposing a one-time consumable event.
@ -153,71 +146,3 @@ private fun Fragment.launch(
) {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(state, block) }
}
const val DEFAULT_TIMEOUT = 60000L
/**
* Wraps [SendChannel.send] with a specified timeout.
*
* @param element The element to send.
* @param timeout The timeout in milliseconds. Defaults to 10 seconds.
* @throws TimeoutException If the timeout is reached, provides context on what element
* specifically.
*/
suspend fun <E> SendChannel<E>.sendWithTimeout(element: E, timeout: Long = DEFAULT_TIMEOUT) {
try {
withTimeout(timeout) { send(element) }
} catch (e: TimeoutCancellationException) {
L.e("Failed to send element to channel $e in ${timeout}ms.")
if (BuildConfig.DEBUG) {
throw TimeoutException("Timed out sending element to channel: $e")
} else {
L.e(e.stackTraceToString())
send(element)
}
}
}
/**
* Wraps a [ReceiveChannel] consumption with a specified timeout. Note that the timeout will only
* start on the first element received, as to prevent initialization of dependent coroutines being
* interpreted as a timeout.
*
* @param action The action to perform on each element received.
* @param timeout The timeout in milliseconds. Defaults to 10 seconds.
* @throws TimeoutException If the timeout is reached, provides context on what element
* specifically.
*/
suspend fun <E> ReceiveChannel<E>.forEachWithTimeout(
timeout: Long = DEFAULT_TIMEOUT,
action: suspend (E) -> Unit
) {
var exhausted = false
var subsequent = false
val handler: suspend () -> Unit = {
val value = receiveCatching()
if (value.isClosed && value.exceptionOrNull() == null) {
exhausted = true
} else {
action(value.getOrThrow())
}
}
while (!exhausted) {
try {
if (subsequent) {
withTimeout(timeout) { handler() }
} else {
handler()
subsequent = true
}
} catch (e: TimeoutCancellationException) {
L.e("Failed to send element to channel $e in ${timeout}ms.")
if (BuildConfig.DEBUG) {
throw TimeoutException("Timed out sending element to channel: $e")
} else {
L.e(e.stackTraceToString())
handler()
}
}
}
}

View file

@ -20,6 +20,7 @@ package org.oxycblt.auxio.widgets
import android.content.res.Resources
import android.graphics.Bitmap
import androidx.core.graphics.scale
import coil3.size.Size
import coil3.transform.Transformation
import kotlin.math.sqrt
@ -49,7 +50,7 @@ class WidgetBitmapTransformation(reduce: Float) : Transformation() {
val scale = sqrt(maxBitmapArea / inputArea.toDouble())
val newWidth = (input.width * scale).toInt()
val newHeight = (input.height * scale).toInt()
return Bitmap.createScaledBitmap(input, newWidth, newHeight, true)
return input.scale(newWidth, newHeight)
}
return input
}

View file

@ -19,7 +19,6 @@
package org.oxycblt.auxio.widgets
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Context
import android.os.Build
@ -66,11 +65,6 @@ fun RemoteViews.setLayoutDirection(@IdRes viewId: Int, layoutDirection: Int) {
setInt(viewId, "setLayoutDirection", layoutDirection)
}
fun AppWidgetManager.setWidgetPreviewCompat(component: ComponentName, remoteViews: RemoteViews) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
setWidgetPreview(component, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, remoteViews)
}
}
/**
* Update the app widget layouts corresponding to the given [WidgetProvider] [ComponentName] with an
* adaptive layout, in a version-compatible manner.

View file

@ -119,7 +119,8 @@
app:layout_constraintEnd_toStartOf="@+id/detail_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_cover" />
app:layout_constraintTop_toBottomOf="@+id/detail_cover"
tools:ignore="RtlSymmetry" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/detail_shuffle_button"

View file

@ -101,7 +101,8 @@
app:icon="@drawable/ic_play_24"
app:layout_constraintEnd_toStartOf="@+id/detail_shuffle_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_info" />
app:layout_constraintTop_toBottomOf="@+id/detail_info"
tools:ignore="RtlSymmetry"/>
<org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/detail_shuffle_button"

View file

@ -129,7 +129,8 @@
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
app:layout_constraintTop_toTopOf="@+id/detail_play_button" />
app:layout_constraintTop_toTopOf="@+id/detail_play_button"
tools:ignore="RtlSymmetry" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.divider.MaterialDivider

View file

@ -132,7 +132,8 @@
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
app:layout_constraintTop_toTopOf="@+id/detail_play_button" />
app:layout_constraintTop_toTopOf="@+id/detail_play_button"
tools:ignore="RtlSymmetry" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.divider.MaterialDivider

View file

@ -34,6 +34,7 @@
android:layout_height="@dimen/size_icon_huge"
android:layout_marginBottom="@dimen/spacing_small"
android:src="@drawable/ic_song_48"
tools:ignore="ContentDescription"
app:tint="?attr/colorOnSurface" />
<TextView

View file

@ -3,12 +3,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_tiny"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:layout_width="wrap_content"
android:layout_height="@dimen/size_touchable_small"
android:scaleType="centerInside"
tools:ignore="ContentDescription"
android:src="@drawable/ui_scroll_thumb" />
</FrameLayout>

View file

@ -22,7 +22,7 @@
android:scaleType="centerCrop"
android:background="@drawable/ui_widget_bg_round"
android:clipToOutline="true"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription,UnusedAttribute" />
<android.widget.LinearLayout
android:id="@+id/widget_panel"

View file

@ -20,8 +20,7 @@
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:background="@drawable/ui_widget_bg_round"
android:clipToOutline="true"
tools:ignore="ContentDescription" />
tools:ignore="ContentDescription,UnusedAttribute" />
<android.widget.LinearLayout
android:id="@+id/widget_panel"

View file

@ -302,7 +302,7 @@
<string name="clr_brown">Kafe</string>
<string name="fmt_db_neg">-%.1f dB</string>
<string name="fmt_bitrate">%d kbps</string>
<string name="fmt_indexing">Duke ngarkuar bibliotekën tuaj muzikore... (%1$d/%2$d)</string>
<string name="fmt_indexing">Duke ngarkuar bibliotekën tuaj muzikore (%1$d/%2$d)</string>
<string name="fmt_lib_song_count">Këngët e ngarkuara: %d</string>
<string name="fmt_lib_album_count">Albumet e ngarkuara: %d</string>
<string name="fmt_lib_artist_count">Artistët e ngarkuar: %d</string>

View file

@ -1,6 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<style name="Theme.Auxio.Black" parent="Theme.Auxio.Base">
<resources xmlns:tools="http://schemas.android.com/tools">
<!--
Can't meaningfully dim the default black theme colors, have to tweak the palette manually
-->
<style name="Theme.Auxio.Black" parent="Theme.Auxio.Base" tools:ignore="PrivateResource">
<item name="colorSurface">@android:color/black</item>
<item name="colorSurfaceDim">@color/m3_ref_palette_dynamic_neutral_variant4</item>
<item name="colorSurfaceBright">@color/m3_ref_palette_dynamic_neutral_variant12</item>

View file

@ -59,8 +59,7 @@ internal interface CacheReadDao {
@Dao
internal interface CacheWriteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun updateSong(CachedSongData: CachedSongData)
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun updateSong(data: CachedSongData)
@Transaction
suspend fun deleteExcludingUris(uris: Set<String>) {

View file

@ -37,6 +37,10 @@ sealed interface Name : Comparable<Name> {
/** A tokenized version of the name that will be compared. */
abstract val tokens: List<Token>
abstract override fun hashCode(): Int
abstract override fun equals(other: Any?): Boolean
final override fun compareTo(other: Name) =
when (other) {
is Known -> {