widgets: revert most widget changes
Revert the introduction of the thin/tiny widgets, but keep the new cover layout I created while working on them. There is simply no way I can cram controls and metadata within the size bucket that the thin widget occupies. I have decided to give up and revert the widget to it's old form. I understand why the thin widget is not appealing. However, the sizing at which a widget can properly accomodate a taller widget is just too precise and not really large enough to justify it's existance.
This commit is contained in:
parent
4c74879cf1
commit
444e4299d6
26 changed files with 201 additions and 231 deletions
|
|
@ -12,7 +12,7 @@
|
|||
#### What's Improved
|
||||
- Re-enabled theme customization on Android 12
|
||||
- The tab selector now hides itself when there is only one tab
|
||||
- Added more buttons to the smallest widget form
|
||||
- Made the cover on the thin widget larger
|
||||
|
||||
#### What's Fixed
|
||||
- Fixed incorrect ellipsizing on song items
|
||||
|
|
|
|||
|
|
@ -24,15 +24,15 @@ android {
|
|||
|
||||
// ExoPlayer needs Java 8 to compile.
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs += "-Xjvm-default=all"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
debuggable true
|
||||
|
|
@ -94,8 +94,7 @@ dependencies {
|
|||
// Exoplayer
|
||||
// WARNING: THE EXOPLAYER VERSION MUST BE KEPT IN LOCK-STEP WITH THE FLAC EXTENSION.
|
||||
// IF NOT, VERY UNFRIENDLY BUILD FAILURES AND CRASHES MAY ENSUE.
|
||||
def exoplayerVersion = "2.17.1"
|
||||
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayerVersion"
|
||||
implementation "com.google.android.exoplayer:exoplayer-core:2.17.1"
|
||||
implementation fileTree(dir: "libs", include: ["extension-*.aar"])
|
||||
|
||||
// Image loading
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
|
|||
}
|
||||
}
|
||||
|
||||
override fun onLibraryChanged() {
|
||||
override fun onLibrarySettingsChanged() {
|
||||
tabs = visibleTabs
|
||||
_shouldRecreateTabs.value = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,8 +66,6 @@ class PlaybackPanelFragment :
|
|||
) {
|
||||
// --- UI SETUP ---
|
||||
|
||||
logD(binding.root.paddingBottom)
|
||||
|
||||
binding.root.setOnApplyWindowInsetsListener { _, insets ->
|
||||
val bars = insets.systemBarInsetsCompat
|
||||
val gestures = insets.systemGestureInsetsCompat
|
||||
|
|
|
|||
|
|
@ -31,8 +31,9 @@ enum class ReplayGainMode {
|
|||
DYNAMIC;
|
||||
|
||||
companion object {
|
||||
fun fromIntCode(value: Int): ReplayGainMode? {
|
||||
return when (value) {
|
||||
/** Convert an int [code] into an instance, or null if it isn't valid. */
|
||||
fun fromIntCode(code: Int): ReplayGainMode? {
|
||||
return when (code) {
|
||||
IntegerTable.REPLAY_GAIN_MODE_OFF -> OFF
|
||||
IntegerTable.REPLAY_GAIN_MODE_TRACK -> TRACK
|
||||
IntegerTable.REPLAY_GAIN_MODE_ALBUM -> ALBUM
|
||||
|
|
@ -42,11 +43,3 @@ enum class ReplayGainMode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents the ReplayGain pre-amp values. */
|
||||
data class ReplayGainPreAmp(
|
||||
/** The value to use when ReplayGain tags are present. */
|
||||
val with: Float,
|
||||
/** The value to use when ReplayGain tags are not present. */
|
||||
val without: Float,
|
||||
)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2022 Auxio Project
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.playback.replaygain
|
||||
|
||||
/** Represents the ReplayGain pre-amp values. */
|
||||
data class ReplayGainPreAmp(
|
||||
/** The value to use when ReplayGain tags are present. */
|
||||
val with: Float,
|
||||
/** The value to use when ReplayGain tags are not present. */
|
||||
val without: Float,
|
||||
)
|
||||
|
|
@ -59,9 +59,9 @@ enum class RepeatMode {
|
|||
}
|
||||
|
||||
companion object {
|
||||
/** Convert an int [constant] into a LoopMode, or null if it isn't valid. */
|
||||
fun fromIntCode(constant: Int): RepeatMode? {
|
||||
return when (constant) {
|
||||
/** Convert an int [code] into an instance, or null if it isn't valid. */
|
||||
fun fromIntCode(code: Int): RepeatMode? {
|
||||
return when (code) {
|
||||
IntegerTable.REPEAT_MODE_NONE -> NONE
|
||||
IntegerTable.REPEAT_MODE_ALL -> ALL
|
||||
IntegerTable.REPEAT_MODE_TRACK -> TRACK
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
|||
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
*/
|
||||
|
|
@ -106,10 +107,21 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
|
|||
.putText(MediaMetadataCompat.METADATA_KEY_COMPOSER, artist)
|
||||
.putText(MediaMetadataCompat.METADATA_KEY_WRITER, artist)
|
||||
.putText(MediaMetadataCompat.METADATA_KEY_GENRE, song.genre.resolveName(context))
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, song.track?.toLong() ?: 0L)
|
||||
.putText(MediaMetadataCompat.METADATA_KEY_DATE, song.album.year?.toString())
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.durationMs)
|
||||
|
||||
if (song.track != null) {
|
||||
metadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, song.track.toLong())
|
||||
}
|
||||
|
||||
if (song.disc != null) {
|
||||
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, song.disc.toLong())
|
||||
}
|
||||
|
||||
if (song.album.year != null) {
|
||||
metadata.putString(
|
||||
MediaMetadataCompat.METADATA_KEY_DATE, unlikelyToBeNull(song.album.year).toString())
|
||||
}
|
||||
|
||||
// Normally, android expects one to provide a URI to the metadata instance instead of
|
||||
// a full blown bitmap. In practice, this is not ideal in the slightest, as we cannot
|
||||
// provide any user customization or quality of life improvements with a flat URI.
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent {
|
|||
}
|
||||
}
|
||||
|
||||
return Accent(prefs.getInt(SettingsManager.KEY_ACCENT, 5))
|
||||
return Accent.from(prefs.getInt(SettingsManager.KEY_ACCENT, 5))
|
||||
}
|
||||
|
||||
/** Cache of the old keys used in Auxio. */
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class SettingsManager private constructor(context: Context) :
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether to display the LoopMode or the shuffle status on the notification. False if loop,
|
||||
* Whether to display the RepeatMode or the shuffle status on the notification. False if repeat,
|
||||
* true if shuffle.
|
||||
*/
|
||||
val useAltNotifAction: Boolean
|
||||
|
|
@ -256,7 +256,7 @@ class SettingsManager private constructor(context: Context) :
|
|||
when (key) {
|
||||
KEY_USE_ALT_NOTIFICATION_ACTION -> callbacks.forEach { it.onNotifSettingsChanged() }
|
||||
KEY_SHOW_COVERS, KEY_QUALITY_COVERS -> callbacks.forEach { it.onCoverSettingsChanged() }
|
||||
KEY_LIB_TABS -> callbacks.forEach { it.onLibraryChanged() }
|
||||
KEY_LIB_TABS -> callbacks.forEach { it.onLibrarySettingsChanged() }
|
||||
KEY_REPLAY_GAIN, KEY_PRE_AMP_WITH, KEY_PRE_AMP_WITHOUT ->
|
||||
callbacks.forEach { it.onReplayGainSettingsChanged() }
|
||||
}
|
||||
|
|
@ -268,7 +268,7 @@ class SettingsManager private constructor(context: Context) :
|
|||
* context.
|
||||
*/
|
||||
interface Callback {
|
||||
fun onLibraryChanged() {}
|
||||
fun onLibrarySettingsChanged() {}
|
||||
fun onNotifSettingsChanged() {}
|
||||
fun onCoverSettingsChanged() {}
|
||||
fun onReplayGainSettingsChanged() {}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
// TODO: Scale this drawable based on available space after padding
|
||||
// FIXME: Scale this drawable based on available space after padding
|
||||
|
||||
imageMatrix =
|
||||
centerMatrix.apply {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,12 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
|
||||
styledAttrs.recycle()
|
||||
|
||||
// Use clipToOutline and a background drawable to crop images. While Coil's transformation
|
||||
// could theoretically be used to round corners, the corner radius is dependent on the
|
||||
// dimensions of the image, which will result in inconsistent corners across different
|
||||
// album covers unless we resize all covers to be the same size. clipToOutline is both
|
||||
// cheaper and more elegant. As a side-note, this also allows us to re-use the same
|
||||
// background for both the tonal background color and the corner rounding.
|
||||
clipToOutline = true
|
||||
background =
|
||||
MaterialShapeDrawable().apply {
|
||||
|
|
@ -79,11 +85,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
|
||||
// Use clipToOutline and a background drawable to crop images. While Coil's transformation
|
||||
// could theoretically be used to round corners, the corner radius is dependent on the
|
||||
// dimensions of the image, which will result in inconsistent corners across different
|
||||
// album covers unless we resize all covers to be the same size. clipToOutline is both
|
||||
// cheaper and more elegant.
|
||||
if (!isInEditMode) {
|
||||
val settingsManager = SettingsManager.getInstance()
|
||||
if (settingsManager.roundCovers) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.oxycblt.auxio.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
|
@ -35,17 +34,39 @@ import org.oxycblt.auxio.util.logD
|
|||
abstract class ViewBindingDialogFragment<T : ViewBinding> : DialogFragment() {
|
||||
private var _binding: T? = null
|
||||
|
||||
/**
|
||||
* Inflate the binding from the given [inflater]. This should usually be done by the binding
|
||||
* implementation's inflate function.
|
||||
*/
|
||||
protected abstract fun onCreateBinding(inflater: LayoutInflater): T
|
||||
protected open fun onBindingCreated(binding: T, savedInstanceState: Bundle?) {}
|
||||
protected open fun onDestroyBinding(binding: T) {}
|
||||
|
||||
/** Called during [onCreateDialog]. Dialog elements should be configured here. */
|
||||
protected open fun onConfigDialog(builder: AlertDialog.Builder) {}
|
||||
|
||||
/**
|
||||
* Called during [onViewCreated] when the binding was successfully inflated and set as the view.
|
||||
* This is where view setup should occur.
|
||||
*/
|
||||
protected open fun onBindingCreated(binding: T, savedInstanceState: Bundle?) {}
|
||||
|
||||
/**
|
||||
* Called during [onDestroyView] when the binding should be destroyed and all callbacks or
|
||||
* leaking elements be released.
|
||||
*/
|
||||
protected open fun onDestroyBinding(binding: T) {}
|
||||
|
||||
/** Maybe get the binding. This will be null outside of the fragment view lifecycle. */
|
||||
protected val binding: T?
|
||||
get() = _binding
|
||||
|
||||
/**
|
||||
* Get the binding under the assumption that the fragment has a view at this state in the
|
||||
* lifecycle. This will throw an exception if the fragment is not in a valid lifecycle.
|
||||
*/
|
||||
protected fun requireBinding(): T {
|
||||
return requireNotNull(_binding) {
|
||||
"ViewBinding was not available, as the fragment was not in a valid state"
|
||||
"ViewBinding was available. Fragment should be a valid state " +
|
||||
"right now, but instead it was ${lifecycle.currentState}"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,12 +76,11 @@ abstract class ViewBindingDialogFragment<T : ViewBinding> : DialogFragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View = onCreateBinding(inflater).also { _binding = it }.root
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return MaterialAlertDialogBuilder(requireActivity(), theme).run {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?) =
|
||||
MaterialAlertDialogBuilder(requireActivity(), theme).run {
|
||||
onConfigDialog(this)
|
||||
create()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
|
@ -73,5 +93,6 @@ abstract class ViewBindingDialogFragment<T : ViewBinding> : DialogFragment() {
|
|||
super.onDestroyView()
|
||||
onDestroyBinding(requireBinding())
|
||||
_binding = null
|
||||
logD("Fragment destroyed")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,16 +32,36 @@ import org.oxycblt.auxio.util.logD
|
|||
abstract class ViewBindingFragment<T : ViewBinding> : Fragment() {
|
||||
private var _binding: T? = null
|
||||
|
||||
/**
|
||||
* Inflate the binding from the given [inflater]. This should usually be done by the binding
|
||||
* implementation's inflate function.
|
||||
*/
|
||||
protected abstract fun onCreateBinding(inflater: LayoutInflater): T
|
||||
|
||||
/**
|
||||
* Called during [onViewCreated] when the binding was successfully inflated and set as the view.
|
||||
* This is where view setup should occur.
|
||||
*/
|
||||
protected open fun onBindingCreated(binding: T, savedInstanceState: Bundle?) {}
|
||||
|
||||
/**
|
||||
* Called during [onDestroyView] when the binding should be destroyed and all callbacks or
|
||||
* leaking elements be released.
|
||||
*/
|
||||
protected open fun onDestroyBinding(binding: T) {}
|
||||
|
||||
/** Maybe get the binding. This will be null outside of the fragment view lifecycle. */
|
||||
protected val binding: T?
|
||||
get() = _binding
|
||||
|
||||
/**
|
||||
* Get the binding under the assumption that the fragment has a view at this state in the
|
||||
* lifecycle. This will throw an exception if the fragment is not in a valid lifecycle.
|
||||
*/
|
||||
protected fun requireBinding(): T {
|
||||
return requireNotNull(_binding) {
|
||||
"ViewBinding was not available, as the fragment was not in a valid state"
|
||||
"ViewBinding was available. Fragment should be a valid state " +
|
||||
"right now, but instead it was ${lifecycle.currentState}"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,5 +81,6 @@ abstract class ViewBindingFragment<T : ViewBinding> : Fragment() {
|
|||
super.onDestroyView()
|
||||
onDestroyBinding(requireBinding())
|
||||
_binding = null
|
||||
logD("Fragment destroyed")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ package org.oxycblt.auxio.ui.accent
|
|||
|
||||
import android.os.Build
|
||||
import org.oxycblt.auxio.R
|
||||
|
||||
val ACCENT_COUNT: Int
|
||||
get() = ACCENT_NAMES.size
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
||||
private val ACCENT_NAMES =
|
||||
intArrayOf(
|
||||
|
|
@ -116,7 +114,7 @@ private val ACCENT_PRIMARY_COLORS =
|
|||
* @property primary The primary color resource for this accent
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
data class Accent(val index: Int) {
|
||||
class Accent private constructor(val index: Int) {
|
||||
val name: Int
|
||||
get() = ACCENT_NAMES[index]
|
||||
val theme: Int
|
||||
|
|
@ -126,7 +124,24 @@ data class Accent(val index: Int) {
|
|||
val primary: Int
|
||||
get() = ACCENT_PRIMARY_COLORS[index]
|
||||
|
||||
override fun equals(other: Any?) = other is Accent && index == other.index
|
||||
|
||||
override fun hashCode() = index.hashCode()
|
||||
|
||||
companion object {
|
||||
fun from(index: Int): Accent {
|
||||
if (index > (MAX - 1)) {
|
||||
logW("Account outside of bounds [idx: $index, max: $MAX")
|
||||
return Accent(5)
|
||||
}
|
||||
|
||||
return Accent(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum amount of accents that are valid. This excludes the dynamic accent on
|
||||
* versions that do not support it.
|
||||
*/
|
||||
val MAX =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ACCENT_THEMES.size
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ import org.oxycblt.auxio.util.getColorSafe
|
|||
import org.oxycblt.auxio.util.inflater
|
||||
import org.oxycblt.auxio.util.stateList
|
||||
|
||||
/** An adapter that displays the accent palette. */
|
||||
/**
|
||||
* An adapter that displays the accent palette.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class AccentAdapter(listener: Listener) :
|
||||
MonoAdapter<Accent, AccentAdapter.Listener, AccentViewHolder>(listener) {
|
||||
var selectedAccent: Accent? = null
|
||||
|
|
@ -64,7 +67,7 @@ class AccentAdapter(listener: Listener) :
|
|||
}
|
||||
|
||||
class AccentData : BackingData<Accent>() {
|
||||
override fun getItem(position: Int) = Accent(position)
|
||||
override fun getItem(position: Int) = Accent.from(position)
|
||||
override fun getItemCount() = Accent.MAX
|
||||
}
|
||||
}
|
||||
|
|
@ -84,15 +87,15 @@ class AccentViewHolder private constructor(private val binding: ItemAccentBindin
|
|||
}
|
||||
|
||||
fun setSelected(isSelected: Boolean) {
|
||||
val context = binding.accent.context
|
||||
|
||||
binding.accent.isEnabled = !isSelected
|
||||
binding.accent.imageTintList =
|
||||
if (isSelected) {
|
||||
context.getAttrColorSafe(R.attr.colorSurface).stateList
|
||||
} else {
|
||||
context.getColorSafe(android.R.color.transparent).stateList
|
||||
}
|
||||
binding.accent.apply {
|
||||
isEnabled = !isSelected
|
||||
imageTintList =
|
||||
if (isSelected) {
|
||||
context.getAttrColorSafe(R.attr.colorSurface).stateList
|
||||
} else {
|
||||
context.getColorSafe(android.R.color.transparent).stateList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -55,17 +55,17 @@ class AccentCustomizeDialog :
|
|||
}
|
||||
|
||||
override fun onBindingCreated(binding: DialogAccentBinding, savedInstanceState: Bundle?) {
|
||||
// --- UI SETUP ---
|
||||
|
||||
binding.accentRecycler.adapter = accentAdapter
|
||||
|
||||
accentAdapter.setSelectedAccent(
|
||||
if (savedInstanceState != null) {
|
||||
Accent(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
||||
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
||||
} else {
|
||||
settingsManager.accent
|
||||
},
|
||||
binding.accentRecycler)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
binding.accentRecycler.adapter = accentAdapter
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
|
|
|||
|
|
@ -222,22 +222,20 @@ fun Context.showToast(@StringRes str: Int) {
|
|||
}
|
||||
|
||||
/** Create a [PendingIntent] that leads to Auxio's [MainActivity] */
|
||||
fun Context.newMainIntent(): PendingIntent {
|
||||
return PendingIntent.getActivity(
|
||||
fun Context.newMainIntent(): PendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
IntegerTable.REQUEST_CODE,
|
||||
Intent(this, MainActivity::class.java),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
}
|
||||
|
||||
/** Create a broadcast [PendingIntent] */
|
||||
fun Context.newBroadcastIntent(what: String): PendingIntent {
|
||||
return PendingIntent.getBroadcast(
|
||||
fun Context.newBroadcastIntent(what: String): PendingIntent =
|
||||
PendingIntent.getBroadcast(
|
||||
this,
|
||||
IntegerTable.REQUEST_CODE,
|
||||
Intent(what),
|
||||
Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
}
|
||||
|
||||
/** Hard-restarts the app. Useful for forcing the app to reload music. */
|
||||
fun Context.hardRestart() {
|
||||
|
|
|
|||
|
|
@ -33,20 +33,13 @@ import org.oxycblt.auxio.util.newMainIntent
|
|||
fun createDefaultWidget(context: Context) = createViews(context, R.layout.widget_default)
|
||||
|
||||
/**
|
||||
* The tiny widget like a small or medium widget, but for landscape or exceptionally small screens.
|
||||
*/
|
||||
fun createTinyWidget(context: Context, state: WidgetComponent.WidgetState) =
|
||||
createViews(context, R.layout.widget_tiny)
|
||||
.applyCover(context, state)
|
||||
.applyBasicControls(context, state)
|
||||
|
||||
/**
|
||||
* The thin widget is like a wide or large widget, but for landscape or exceptionally small screens.
|
||||
* The thin widget is a weird outlier widget intended to work well on strange launchers or
|
||||
* landscape grid launchers that allow really thin widget sizing.
|
||||
*/
|
||||
fun createThinWidget(context: Context, state: WidgetComponent.WidgetState) =
|
||||
createViews(context, R.layout.widget_thin)
|
||||
.applyCover(context, state)
|
||||
.applyFullControls(context, state)
|
||||
.applyMeta(context, state)
|
||||
.applyBasicControls(context, state)
|
||||
|
||||
/**
|
||||
* The small widget is for 2x2 widgets and just shows the cover art and playback controls. This is
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ class WidgetProvider : AppWidgetProvider() {
|
|||
// Map each widget form to the cells where it would look at least okay.
|
||||
val views =
|
||||
mapOf(
|
||||
SizeF(180f, 100f) to createTinyWidget(context, state),
|
||||
SizeF(372f, 100f) to createThinWidget(context, state),
|
||||
SizeF(180f, 100f) to createThinWidget(context, state),
|
||||
SizeF(180f, 152f) to createSmallWidget(context, state),
|
||||
SizeF(272f, 152f) to createWideWidget(context, state),
|
||||
SizeF(180f, 270f) to createMediumWidget(context, state),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<!--
|
||||
See widget_small.xml for an explanation for the ImageView setup
|
||||
See widget_small.xml for an explanation for the ImageView setup.
|
||||
-->
|
||||
|
||||
<android.widget.ImageView
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<!--
|
||||
See widget_small.xml for an explanation for the ImageView setup
|
||||
See widget_small.xml for an explanation for the ImageView setup.
|
||||
-->
|
||||
|
||||
<android.widget.ImageView
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<!--
|
||||
For this widget form to work, we need to scale the ImageView across a 1:1 aspect ratio.
|
||||
For most widget forms to work, we need to scale the ImageView across a 1:1 aspect ratio.
|
||||
However, since we are working with a RemoteViews instance, we can't just use ConstraintLayout
|
||||
to achieve this. We can use RelativeLayout, but there's no way to force an aspect ratio with
|
||||
that layout. However, if we create an invisible ImageView that contains a massive fixed-size
|
||||
|
|
|
|||
|
|
@ -5,22 +5,22 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface"
|
||||
android:theme="@style/Theme.Widget"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<!--
|
||||
Wrapping the 1:1 ImageView hack in a LinearLayout allows the view to measure greedily
|
||||
without squishing the controls.
|
||||
-->
|
||||
|
||||
<RelativeLayout
|
||||
<android.widget.RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<!--
|
||||
See widget_small.xml for an explanation for the ImageView setup
|
||||
See widget_small.xml for an explanation for the ImageView setup.
|
||||
-->
|
||||
|
||||
<android.widget.ImageView
|
||||
|
|
@ -52,63 +52,53 @@
|
|||
android:src="@drawable/ic_remote_default_cover"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</RelativeLayout>
|
||||
</android.widget.RelativeLayout>
|
||||
|
||||
|
||||
<android.widget.LinearLayout
|
||||
android:id="@+id/widget_panel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:layout_marginEnd="@dimen/spacing_medium"
|
||||
android:layout_marginBottom="@dimen/spacing_medium"
|
||||
android:layout_marginEnd="@dimen/spacing_medium">
|
||||
android:layout_weight="2"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_repeat"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
<android.widget.LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_change_repeat"
|
||||
android:src="@drawable/ic_repeat" />
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_skip_prev"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_skip_prev"
|
||||
android:src="@drawable/ic_skip_prev" />
|
||||
<android.widget.TextView
|
||||
android:id="@+id/widget_song"
|
||||
style="@style/Widget.Auxio.TextView.Primary.AppWidget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/def_widget_song" />
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_play_pause"
|
||||
android:src="@drawable/sel_playing_state" />
|
||||
<android.widget.TextView
|
||||
android:id="@+id/widget_artist"
|
||||
style="@style/Widget.Auxio.TextView.Secondary.AppWidget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/def_widget_artist" />
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_skip_next"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_skip_next"
|
||||
android:src="@drawable/ic_skip_next" />
|
||||
</android.widget.LinearLayout>
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="@dimen/size_btn_small"
|
||||
android:minHeight="@dimen/size_btn_small"
|
||||
android:contentDescription="@string/desc_play_pause"
|
||||
android:src="@drawable/ic_play" />
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_shuffle"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_shuffle"
|
||||
android:src="@drawable/ic_shuffle_state" />
|
||||
|
||||
</android.widget.LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@android:id/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/colorSurface"
|
||||
android:theme="@style/Theme.Widget"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<!--
|
||||
Wrapping the 1:1 ImageView hack in a LinearLayout allows the view to measure greedily
|
||||
without squishing the controls.
|
||||
-->
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<!--
|
||||
See widget_small.xml for an explanation for the ImageView setup
|
||||
-->
|
||||
|
||||
<android.widget.ImageView
|
||||
android:id="@+id/widget_aspect_ratio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="@dimen/spacing_medium"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:layout_marginEnd="@dimen/spacing_medium"
|
||||
android:layout_marginBottom="@dimen/spacing_medium"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ui_remote_aspect_ratio"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<android.widget.ImageView
|
||||
android:id="@+id/widget_cover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignStart="@id/widget_aspect_ratio"
|
||||
android:layout_alignTop="@id/widget_aspect_ratio"
|
||||
android:layout_alignEnd="@id/widget_aspect_ratio"
|
||||
android:layout_alignBottom="@id/widget_aspect_ratio"
|
||||
android:src="@drawable/ic_remote_default_cover"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<android.widget.LinearLayout
|
||||
android:id="@+id/widget_panel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:layout_marginBottom="@dimen/spacing_medium"
|
||||
android:layout_marginEnd="@dimen/spacing_medium">
|
||||
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_skip_prev"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_skip_prev"
|
||||
android:src="@drawable/ic_skip_prev" />
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_play_pause"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_play_pause"
|
||||
android:src="@drawable/sel_playing_state" />
|
||||
|
||||
<android.widget.ImageButton
|
||||
android:id="@+id/widget_skip_next"
|
||||
style="@style/Widget.Auxio.PlaybackButton.AppWidget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/desc_skip_next"
|
||||
android:src="@drawable/ic_skip_next" />
|
||||
|
||||
</android.widget.LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
android:theme="@style/Theme.Widget">
|
||||
|
||||
<!--
|
||||
See widget_small.xml for an explanation for the ImageView setup
|
||||
See widget_small.xml for an explanation for the ImageView setup.
|
||||
-->
|
||||
|
||||
<android.widget.ImageView
|
||||
|
|
|
|||
Loading…
Reference in a new issue