diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf657f5de..febae9e63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
## dev [v2.2.3, v2.3.0, or v3.0.0]
+#### Dev/Meta
+- Switched to spotless and ktfmt instead of ktlint
+
## v2.2.2
#### What's New
- New spanish translations and metadata [courtesy of n-berenice]
diff --git a/app/NOTICE b/app/NOTICE
new file mode 100644
index 000000000..dc9b86ca7
--- /dev/null
+++ b/app/NOTICE
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) $today.year 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 .
+ */
+
diff --git a/app/build.gradle b/app/build.gradle
index 378a4bf6e..a5ba391eb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,6 +2,7 @@ apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: "kotlin-kapt"
apply plugin: "androidx.navigation.safeargs.kotlin"
+apply plugin: "com.diffplug.spotless"
android {
compileSdkVersion 32
@@ -45,12 +46,8 @@ android {
}
}
-configurations {
- ktlint
-}
-
afterEvaluate {
- preDebugBuild.dependsOn ktlintFormat
+ preDebugBuild.dependsOn spotlessApply
}
dependencies {
@@ -104,24 +101,13 @@ dependencies {
// Material
implementation "com.google.android.material:material:1.6.0-alpha03"
-
- // --- DEBUG ---
-
- // Lint
- ktlint "com.pinterest:ktlint:0.44.0"
}
-task ktlint(type: JavaExec, group: "verification") {
- description = "Check Kotlin code style."
- mainClass.set("com.pinterest.ktlint.Main")
- classpath = configurations.ktlint
- args "src/**/*.kt"
-}
-check.dependsOn ktlint
+spotless {
+ kotlin {
+ target "src/**/*.kt"
-task ktlintFormat(type: JavaExec, group: "formatting") {
- description = "Fix Kotlin code style deviations."
- mainClass.set("com.pinterest.ktlint.Main")
- classpath = configurations.ktlint
- args "-F", "src/**/*.kt"
+ ktfmt('0.30').dropboxStyle()
+ licenseHeaderFile("NOTICE")
+ }
}
diff --git a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt
index b12f32a4f..e69342276 100644
--- a/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt
+++ b/app/src/main/java/org/oxycblt/auxio/AuxioApp.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AuxioApp.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio
import android.app.Application
@@ -31,10 +30,12 @@ import org.oxycblt.auxio.settings.SettingsManager
/**
* TODO: Plan for a general UI rework
+ * ```
* - Refactor fragment class
* - Remove databinding and dedup layouts
* - Rework RecyclerView management and item dragging
* - Rework sealed classes to minimize whens and maximize overrides
+ * ```
*/
@Suppress("UNUSED")
class AuxioApp : Application(), ImageLoaderFactory {
diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
index a0d47383b..9c294b41d 100644
--- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
+++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * MainActivity.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio
import android.content.Intent
@@ -39,10 +38,8 @@ import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
import org.oxycblt.auxio.util.systemBarInsetsCompat
/**
- * The single [AppCompatActivity] for Auxio.
- * TODO: Add a new view for crashes with a stack trace
- * TODO: Custom language support
- * TODO: Rework menus [perhaps add multi-select]
+ * The single [AppCompatActivity] for Auxio. TODO: Add a new view for crashes with a stack trace
+ * TODO: Custom language support TODO: Rework menus [perhaps add multi-select]
*/
class MainActivity : AppCompatActivity() {
private val playbackModel: PlaybackViewModel by viewModels()
@@ -52,9 +49,8 @@ class MainActivity : AppCompatActivity() {
setupTheme()
- val binding = DataBindingUtil.setContentView(
- this, R.layout.activity_main
- )
+ val binding =
+ DataBindingUtil.setContentView(this, R.layout.activity_main)
applyEdgeToEdgeWindow(binding)
@@ -82,9 +78,7 @@ class MainActivity : AppCompatActivity() {
if (action == Intent.ACTION_VIEW && !isConsumed) {
// Mark the intent as used so this does not fire again
intent.putExtra(KEY_INTENT_USED, true)
- intent.data?.let { fileUri ->
- playbackModel.playWithUri(fileUri, this)
- }
+ intent.data?.let { fileUri -> playbackModel.playWithUri(fileUri, this) }
}
}
}
@@ -129,12 +123,10 @@ class MainActivity : AppCompatActivity() {
WindowInsets.Builder()
.setInsets(
WindowInsets.Type.systemBars(),
- insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
- )
+ insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()))
.setInsets(
WindowInsets.Type.systemGestures(),
- insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemGestures())
- )
+ insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemGestures()))
.build()
.applyLeftRightInsets(binding)
}
@@ -144,12 +136,10 @@ class MainActivity : AppCompatActivity() {
@Suppress("DEPRECATION")
binding.root.apply {
- systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ systemUiVisibility =
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- setOnApplyWindowInsetsListener { _, insets ->
- insets.applyLeftRightInsets(binding)
- }
+ setOnApplyWindowInsetsListener { _, insets -> insets.applyLeftRightInsets(binding) }
}
}
}
@@ -157,10 +147,7 @@ class MainActivity : AppCompatActivity() {
private fun WindowInsets.applyLeftRightInsets(binding: ViewBinding): WindowInsets {
val bars = systemBarInsetsCompat
- binding.root.updatePadding(
- left = bars.left,
- right = bars.right
- )
+ binding.root.updatePadding(left = bars.left, right = bars.right)
return replaceSystemBarInsetsCompat(0, bars.top, 0, bars.bottom)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
index ac0657cf3..1594a94c5 100644
--- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * MainFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio
import android.Manifest
@@ -39,10 +38,10 @@ import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
/**
- * A wrapper around the home fragment that shows the playback fragment and controls
- * the more high-level navigation features.
- * @author OxygenCobalt
- * TODO: Add a new view with a stack trace whenever the music loading process fails.
+ * A wrapper around the home fragment that shows the playback fragment and controls the more
+ * high-level navigation features.
+ * @author OxygenCobalt TODO: Add a new view with a stack trace whenever the music loading process
+ * fails.
*/
class MainFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
@@ -58,22 +57,18 @@ class MainFragment : Fragment() {
val binding = FragmentMainBinding.inflate(inflater)
// Build the permission launcher here as you can only do it in onCreateView/onCreate
- val permLauncher = registerForActivityResult(
- ActivityResultContracts.RequestPermission()
- ) {
- musicModel.reloadMusic(requireContext())
- }
+ val permLauncher =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) {
+ musicModel.reloadMusic(requireContext())
+ }
// --- UI SETUP ---
binding.lifecycleOwner = viewLifecycleOwner
- requireActivity().onBackPressedDispatcher.addCallback(
- viewLifecycleOwner,
- Callback(binding).also {
- callback = it
- }
- )
+ requireActivity()
+ .onBackPressedDispatcher
+ .addCallback(viewLifecycleOwner, Callback(binding).also { callback = it })
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Auxio's layout completely breaks down when it's window is resized too small,
@@ -110,15 +105,15 @@ class MainFragment : Fragment() {
is MusicStore.Response.Err -> {
logW("Received Error")
- val errorRes = when (response.kind) {
- MusicStore.ErrorKind.NO_MUSIC -> R.string.err_no_music
- MusicStore.ErrorKind.NO_PERMS -> R.string.err_no_perms
- MusicStore.ErrorKind.FAILED -> R.string.err_load_failed
- }
+ val errorRes =
+ when (response.kind) {
+ MusicStore.ErrorKind.NO_MUSIC -> R.string.err_no_music
+ MusicStore.ErrorKind.NO_PERMS -> R.string.err_no_perms
+ MusicStore.ErrorKind.FAILED -> R.string.err_load_failed
+ }
- val snackbar = Snackbar.make(
- binding.root, getString(errorRes), Snackbar.LENGTH_INDEFINITE
- )
+ val snackbar =
+ Snackbar.make(binding.root, getString(errorRes), Snackbar.LENGTH_INDEFINITE)
when (response.kind) {
MusicStore.ErrorKind.FAILED, MusicStore.ErrorKind.NO_MUSIC -> {
@@ -126,7 +121,6 @@ class MainFragment : Fragment() {
musicModel.reloadMusic(requireContext())
}
}
-
MusicStore.ErrorKind.NO_PERMS -> {
snackbar.setAction(R.string.lbl_grant) {
permLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
@@ -164,7 +158,8 @@ class MainFragment : Fragment() {
if (!binding.playbackLayout.collapse()) {
val navController = binding.exploreNavHost.findNavController()
- if (navController.currentDestination?.id == navController.graph.startDestinationId) {
+ if (navController.currentDestination?.id ==
+ navController.graph.startDestinationId) {
isEnabled = false
requireActivity().onBackPressed()
isEnabled = true
diff --git a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt
index e496f8193..20ecf8149 100644
--- a/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt
+++ b/app/src/main/java/org/oxycblt/auxio/accent/Accent.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Accent.kt is part of Auxio.
*
* 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
@@ -15,103 +14,112 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.accent
import org.oxycblt.auxio.R
-val ACCENT_COUNT: Int get() = ACCENT_NAMES.size
+val ACCENT_COUNT: Int
+ get() = ACCENT_NAMES.size
-private val ACCENT_NAMES = arrayOf(
- R.string.clr_red,
- R.string.clr_pink,
- R.string.clr_purple,
- R.string.clr_deep_purple,
- R.string.clr_indigo,
- R.string.clr_blue,
- R.string.clr_deep_blue,
- R.string.clr_cyan,
- R.string.clr_teal,
- R.string.clr_green,
- R.string.clr_deep_green,
- R.string.clr_lime,
- R.string.clr_yellow,
- R.string.clr_orange,
- R.string.clr_brown,
- R.string.clr_grey,
-)
+private val ACCENT_NAMES =
+ arrayOf(
+ R.string.clr_red,
+ R.string.clr_pink,
+ R.string.clr_purple,
+ R.string.clr_deep_purple,
+ R.string.clr_indigo,
+ R.string.clr_blue,
+ R.string.clr_deep_blue,
+ R.string.clr_cyan,
+ R.string.clr_teal,
+ R.string.clr_green,
+ R.string.clr_deep_green,
+ R.string.clr_lime,
+ R.string.clr_yellow,
+ R.string.clr_orange,
+ R.string.clr_brown,
+ R.string.clr_grey,
+ )
-private val ACCENT_THEMES = arrayOf(
- R.style.Theme_Auxio_Red,
- R.style.Theme_Auxio_Pink,
- R.style.Theme_Auxio_Purple,
- R.style.Theme_Auxio_DeepPurple,
- R.style.Theme_Auxio_Indigo,
- R.style.Theme_Auxio_Blue,
- R.style.Theme_Auxio_DeepBlue,
- R.style.Theme_Auxio_Cyan,
- R.style.Theme_Auxio_Teal,
- R.style.Theme_Auxio_Green,
- R.style.Theme_Auxio_DeepGreen,
- R.style.Theme_Auxio_Lime,
- R.style.Theme_Auxio_Yellow,
- R.style.Theme_Auxio_Orange,
- R.style.Theme_Auxio_Brown,
- R.style.Theme_Auxio_Grey,
-)
+private val ACCENT_THEMES =
+ arrayOf(
+ R.style.Theme_Auxio_Red,
+ R.style.Theme_Auxio_Pink,
+ R.style.Theme_Auxio_Purple,
+ R.style.Theme_Auxio_DeepPurple,
+ R.style.Theme_Auxio_Indigo,
+ R.style.Theme_Auxio_Blue,
+ R.style.Theme_Auxio_DeepBlue,
+ R.style.Theme_Auxio_Cyan,
+ R.style.Theme_Auxio_Teal,
+ R.style.Theme_Auxio_Green,
+ R.style.Theme_Auxio_DeepGreen,
+ R.style.Theme_Auxio_Lime,
+ R.style.Theme_Auxio_Yellow,
+ R.style.Theme_Auxio_Orange,
+ R.style.Theme_Auxio_Brown,
+ R.style.Theme_Auxio_Grey,
+ )
-private val ACCENT_BLACK_THEMES = arrayOf(
- R.style.Theme_Auxio_Black_Red,
- R.style.Theme_Auxio_Black_Pink,
- R.style.Theme_Auxio_Black_Purple,
- R.style.Theme_Auxio_Black_DeepPurple,
- R.style.Theme_Auxio_Black_Indigo,
- R.style.Theme_Auxio_Black_Blue,
- R.style.Theme_Auxio_Black_DeepBlue,
- R.style.Theme_Auxio_Black_Cyan,
- R.style.Theme_Auxio_Black_Teal,
- R.style.Theme_Auxio_Black_Green,
- R.style.Theme_Auxio_Black_DeepGreen,
- R.style.Theme_Auxio_Black_Lime,
- R.style.Theme_Auxio_Black_Yellow,
- R.style.Theme_Auxio_Black_Orange,
- R.style.Theme_Auxio_Black_Brown,
- R.style.Theme_Auxio_Black_Grey,
-)
+private val ACCENT_BLACK_THEMES =
+ arrayOf(
+ R.style.Theme_Auxio_Black_Red,
+ R.style.Theme_Auxio_Black_Pink,
+ R.style.Theme_Auxio_Black_Purple,
+ R.style.Theme_Auxio_Black_DeepPurple,
+ R.style.Theme_Auxio_Black_Indigo,
+ R.style.Theme_Auxio_Black_Blue,
+ R.style.Theme_Auxio_Black_DeepBlue,
+ R.style.Theme_Auxio_Black_Cyan,
+ R.style.Theme_Auxio_Black_Teal,
+ R.style.Theme_Auxio_Black_Green,
+ R.style.Theme_Auxio_Black_DeepGreen,
+ R.style.Theme_Auxio_Black_Lime,
+ R.style.Theme_Auxio_Black_Yellow,
+ R.style.Theme_Auxio_Black_Orange,
+ R.style.Theme_Auxio_Black_Brown,
+ R.style.Theme_Auxio_Black_Grey,
+ )
-private val ACCENT_PRIMARY_COLORS = arrayOf(
- R.color.red_primary,
- R.color.pink_primary,
- R.color.purple_primary,
- R.color.deep_purple_primary,
- R.color.indigo_primary,
- R.color.blue_primary,
- R.color.deep_blue_primary,
- R.color.cyan_primary,
- R.color.teal_primary,
- R.color.green_primary,
- R.color.deep_green_primary,
- R.color.lime_primary,
- R.color.yellow_primary,
- R.color.orange_primary,
- R.color.brown_primary,
- R.color.grey_primary,
-)
+private val ACCENT_PRIMARY_COLORS =
+ arrayOf(
+ R.color.red_primary,
+ R.color.pink_primary,
+ R.color.purple_primary,
+ R.color.deep_purple_primary,
+ R.color.indigo_primary,
+ R.color.blue_primary,
+ R.color.deep_blue_primary,
+ R.color.cyan_primary,
+ R.color.teal_primary,
+ R.color.green_primary,
+ R.color.deep_green_primary,
+ R.color.lime_primary,
+ R.color.yellow_primary,
+ R.color.orange_primary,
+ R.color.brown_primary,
+ R.color.grey_primary,
+ )
/**
- * The data object for an accent. In the UI this is known as a "Color Scheme."
- * This can be nominally used to gleam some attributes about a given color scheme, but this
- * is not recommended. Attributes are the better option in nearly all cases.
+ * The data object for an accent. In the UI this is known as a "Color Scheme." This can be nominally
+ * used to gleam some attributes about a given color scheme, but this is not recommended. Attributes
+ * are the better option in nearly all cases.
*
- * @property name The name of this accent
+ * @property name The name of this accent
* @property theme The theme resource for this accent
* @property blackTheme The black theme resource for this accent
* @property primary The primary color resource for this accent
* @author OxygenCobalt
*/
data class Accent(val index: Int) {
- val name: Int get() = ACCENT_NAMES[index]
- val theme: Int get() = ACCENT_THEMES[index]
- val blackTheme: Int get() = ACCENT_BLACK_THEMES[index]
- val primary: Int get() = ACCENT_PRIMARY_COLORS[index]
+ val name: Int
+ get() = ACCENT_NAMES[index]
+ val theme: Int
+ get() = ACCENT_THEMES[index]
+ val blackTheme: Int
+ get() = ACCENT_BLACK_THEMES[index]
+ val primary: Int
+ get() = ACCENT_PRIMARY_COLORS[index]
}
diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt
index 0eaa56242..18a6fb486 100644
--- a/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AccentAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.accent
import android.view.ViewGroup
@@ -33,10 +32,8 @@ import org.oxycblt.auxio.util.stateList
* @author OxygenCobalt
* @param onSelect What to do when an accent is selected.
*/
-class AccentAdapter(
- private var curAccent: Accent,
- private val onSelect: (accent: Accent) -> Unit
-) : RecyclerView.Adapter() {
+class AccentAdapter(private var curAccent: Accent, private val onSelect: (accent: Accent) -> Unit) :
+ RecyclerView.Adapter() {
private var selectedViewHolder: ViewHolder? = null
override fun getItemCount(): Int = ACCENT_COUNT
@@ -54,9 +51,8 @@ class AccentAdapter(
onSelect(accent)
}
- inner class ViewHolder(
- private val binding: ItemAccentBinding
- ) : RecyclerView.ViewHolder(binding.root) {
+ inner class ViewHolder(private val binding: ItemAccentBinding) :
+ RecyclerView.ViewHolder(binding.root) {
fun bind(accent: Accent) {
setSelected(accent == curAccent)
@@ -77,14 +73,15 @@ class AccentAdapter(
val context = binding.accent.context
binding.accent.isEnabled = !isSelected
- binding.accent.imageTintList = if (isSelected) {
- // Switch out the currently selected ViewHolder with this one.
- selectedViewHolder?.setSelected(false)
- selectedViewHolder = this
- context.getAttrColorSafe(R.attr.colorSurface).stateList
- } else {
- context.getColorSafe(android.R.color.transparent).stateList
- }
+ binding.accent.imageTintList =
+ if (isSelected) {
+ // Switch out the currently selected ViewHolder with this one.
+ selectedViewHolder?.setSelected(false)
+ selectedViewHolder = this
+ context.getAttrColorSafe(R.attr.colorSurface).stateList
+ } else {
+ context.getColorSafe(android.R.color.transparent).stateList
+ }
}
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt
index 6c2321e5a..ed15a5bb9 100644
--- a/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentCustomizeDialog.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AccentDialog.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.accent
import android.os.Bundle
@@ -52,10 +51,11 @@ class AccentCustomizeDialog : LifecycleDialog() {
// --- UI SETUP ---
binding.accentRecycler.apply {
- adapter = AccentAdapter(pendingAccent) { accent ->
- logD("Switching selected accent to $accent")
- pendingAccent = accent
- }
+ adapter =
+ AccentAdapter(pendingAccent) { accent ->
+ logD("Switching selected accent to $accent")
+ pendingAccent = accent
+ }
}
logD("Dialog created")
diff --git a/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt b/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt
index 48ed253ee..ad9c91db0 100644
--- a/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt
+++ b/app/src/main/java/org/oxycblt/auxio/accent/AccentGridLayoutManager.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AutoGridLayoutManager.kt is part of Auxio.
*
* 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
@@ -15,20 +14,20 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.accent
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import org.oxycblt.auxio.util.pxOfDp
import kotlin.math.max
+import org.oxycblt.auxio.util.pxOfDp
/**
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width
- * of the RecyclerView.
- * Adapted from this StackOverflow answer: https://stackoverflow.com/a/30256880/14143986
+ * of the RecyclerView. Adapted from this StackOverflow answer:
+ * https://stackoverflow.com/a/30256880/14143986
*/
class AccentGridLayoutManager(
context: Context,
diff --git a/app/src/main/java/org/oxycblt/auxio/coil/BaseFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/BaseFetcher.kt
index 71801b6d1..1aa9ddc8b 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/BaseFetcher.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/BaseFetcher.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.coil
import android.content.Context
@@ -5,6 +22,7 @@ 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
@@ -20,6 +38,8 @@ 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
@@ -28,22 +48,17 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.settings.SettingsManager
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.
- * @author OxygenCobalt
- * TODO: Artist images
+ * @author OxygenCobalt TODO: Artist images
*/
abstract class BaseFetcher : Fetcher {
private val settingsManager = SettingsManager.getInstance()
/**
- * Fetch the artwork of an [album].
- * This call respects user configuration and has proper redundancy in the case that
- * an API fails to load.
+ * Fetch the artwork of an [album]. This call respects user configuration and has proper
+ * redundancy in the case that an API fails to load.
*/
protected suspend fun fetchArt(context: Context, album: Album): InputStream? {
if (!settingsManager.showCovers) {
@@ -67,9 +82,7 @@ abstract class BaseFetcher : Fetcher {
val uri = data.albumCoverUri
// Eliminate any chance that this blocking call might mess up the cancellation process
- return withContext(Dispatchers.IO) {
- context.contentResolver.openInputStream(uri)
- }
+ return withContext(Dispatchers.IO) { context.contentResolver.openInputStream(uri) }
}
private suspend fun fetchQualityCovers(context: Context, album: Album): InputStream? {
@@ -115,17 +128,13 @@ abstract class BaseFetcher : Fetcher {
// Get the embedded picture from MediaMetadataRetriever, which will return a full
// ByteArray of the cover without any compression artifacts.
// If its null [i.e there is no embedded cover], than just ignore it and move on
- return ext.embeddedPicture?.let { coverBytes ->
- ByteArrayInputStream(coverBytes)
- }
+ return ext.embeddedPicture?.let { coverBytes -> ByteArrayInputStream(coverBytes) }
}
}
private suspend fun fetchExoplayerCover(context: Context, album: Album): InputStream? {
val uri = album.songs[0].uri
- val future = MetadataRetriever.retrieveMetadata(
- context, MediaItem.fromUri(uri)
- )
+ val future = MetadataRetriever.retrieveMetadata(context, MediaItem.fromUri(uri))
// future.get is a blocking call that makes us spin until the future is done.
// This is bad for a co-routine, as it prevents cancellation and by extension
@@ -133,13 +142,14 @@ abstract class BaseFetcher : Fetcher {
// To fix this we wrap this around in a withContext call to make it suspend and make
// sure that the runner can do other coroutines.
@Suppress("BlockingMethodInNonBlockingContext")
- val tracks = withContext(Dispatchers.IO) {
- try {
- future.get()
- } catch (e: Exception) {
- null
+ val tracks =
+ withContext(Dispatchers.IO) {
+ try {
+ future.get()
+ } catch (e: Exception) {
+ null
+ }
}
- }
if (tracks == null || tracks.isEmpty) {
// Unrecognized format. This is expected, as ExoPlayer only supports a
@@ -201,14 +211,17 @@ abstract class BaseFetcher : Fetcher {
* Create a mosaic image from multiple streams of image data, Code adapted from Phonograph
* https://github.com/kabouzeid/Phonograph
*/
- protected suspend fun createMosaic(context: Context, streams: List, size: Size): FetchResult? {
+ protected suspend fun createMosaic(
+ context: Context,
+ streams: List,
+ size: Size
+ ): FetchResult? {
if (streams.size < 4) {
return streams.firstOrNull()?.let { stream ->
return SourceResult(
source = ImageSource(stream.source().buffer(), context),
mimeType = null,
- dataSource = DataSource.DISK
- )
+ dataSource = DataSource.DISK)
}
}
@@ -216,15 +229,11 @@ abstract class BaseFetcher : Fetcher {
// get a symmetrical mosaic [and to prevent bugs]. If there is no size, default to a
// 512x512 mosaic.
val mosaicSize = AndroidSize(size.width.mosaicSize(), size.height.mosaicSize())
- val mosaicFrameSize = Size(
- Dimension(mosaicSize.width / 2), Dimension(mosaicSize.height / 2)
- )
+ 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 =
+ Bitmap.createBitmap(mosaicSize.width, mosaicSize.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(mosaicBitmap)
var x = 0
@@ -239,11 +248,9 @@ abstract class BaseFetcher : Fetcher {
// Run the bitmap through a transform to make sure it's a square of the desired
// resolution.
- val bitmap = SquareFrameTransform.INSTANCE
- .transform(
- BitmapFactory.decodeStream(stream),
- mosaicFrameSize
- )
+ val bitmap =
+ SquareFrameTransform.INSTANCE.transform(
+ BitmapFactory.decodeStream(stream), mosaicFrameSize)
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
@@ -261,8 +268,7 @@ abstract class BaseFetcher : Fetcher {
return DrawableResult(
drawable = mosaicBitmap.toDrawable(context.resources),
isSampled = true,
- dataSource = DataSource.DISK
- )
+ dataSource = DataSource.DISK)
}
private fun Dimension.mosaicSize(): Int {
diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt
index 256ff621f..33aa4bda3 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * CoilUtils.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.coil
import android.content.Context
@@ -38,27 +37,19 @@ import org.oxycblt.auxio.music.Song
// --- BINDING ADAPTERS ---
-/**
- * Bind the album art for a [song].
- */
+/** Bind the album art for a [song]. */
@BindingAdapter("albumArt")
fun ImageView.bindAlbumArt(song: Song?) = load(song, R.drawable.ic_album)
-/**
- * Bind the album art for an [album].
- */
+/** Bind the album art for an [album]. */
@BindingAdapter("albumArt")
fun ImageView.bindAlbumArt(album: Album?) = load(album, R.drawable.ic_album)
-/**
- * Bind the image for an [artist]
- */
+/** Bind the image for an [artist] */
@BindingAdapter("artistImage")
fun ImageView.bindArtistImage(artist: Artist?) = load(artist, R.drawable.ic_artist)
-/**
- * Bind the image for a [genre]
- */
+/** Bind the image for a [genre] */
@BindingAdapter("genreImage")
fun ImageView.bindGenreImage(genre: Genre?) = load(genre, R.drawable.ic_genre)
@@ -74,23 +65,14 @@ fun ImageView.load(music: T?, @DrawableRes error: Int) {
/**
* Get a bitmap for a [song]. [onDone] will be called with the loaded bitmap, or null if loading
- * failed/shouldn't occur.
- * **This not meant for UIs, instead use the Binding Adapters.**
+ * failed/shouldn't occur. **This not meant for UIs, instead use the Binding Adapters.**
*/
-fun loadBitmap(
- context: Context,
- song: Song,
- onDone: (Bitmap?) -> Unit
-) {
+fun loadBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) {
context.imageLoader.enqueue(
ImageRequest.Builder(context)
.data(song.album)
.size(Size.ORIGINAL)
.transformations(SquareFrameTransform())
- .target(
- onError = { onDone(null) },
- onSuccess = { onDone(it.toBitmap()) }
- )
- .build()
- )
+ .target(onError = { onDone(null) }, onSuccess = { onDone(it.toBitmap()) })
+ .build())
}
diff --git a/app/src/main/java/org/oxycblt/auxio/coil/CrossfadeFactory.kt b/app/src/main/java/org/oxycblt/auxio/coil/CrossfadeFactory.kt
index 8b2f03716..a5b1e730c 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/CrossfadeFactory.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/CrossfadeFactory.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.coil
import coil.decode.DataSource
@@ -9,8 +26,8 @@ import coil.transition.Transition
import coil.transition.TransitionTarget
/**
- * A copy of [CrossfadeTransition.Factory] that applies a transition to error results.
- * You know. Like they used to.
+ * A copy of [CrossfadeTransition.Factory] that applies a transition to error results. You know.
+ * Like they used to.
* @author Coil Team
*/
class CrossfadeFactory : Transition.Factory {
diff --git a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt
index 7e56c4ddf..387ac275c 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Fetchers.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.coil
import android.content.Context
@@ -27,6 +26,7 @@ import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.request.Options
import coil.size.Size
+import kotlin.math.min
import okio.buffer
import okio.source
import org.oxycblt.auxio.music.Album
@@ -34,23 +34,19 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.Sort
-import kotlin.math.min
/**
* Fetcher that returns the album art for a given [Album] or [Song], depending on the factory used.
* @author OxygenCobalt
*/
-class AlbumArtFetcher private constructor(
- private val context: Context,
- private val album: Album
-) : BaseFetcher() {
+class AlbumArtFetcher private constructor(private val context: Context, private val album: Album) :
+ BaseFetcher() {
override suspend fun fetch(): FetchResult? {
return fetchArt(context, album)?.let { stream ->
SourceResult(
source = ImageSource(stream.source().buffer(), context),
mimeType = null,
- dataSource = DataSource.DISK
- )
+ dataSource = DataSource.DISK)
}
}
@@ -71,17 +67,15 @@ class AlbumArtFetcher private constructor(
* Fetcher that fetches the image for an [Artist]
* @author OxygenCobalt
*/
-class ArtistImageFetcher private constructor(
+class ArtistImageFetcher
+private constructor(
private val context: Context,
private val size: Size,
private val artist: Artist,
) : BaseFetcher() {
override suspend fun fetch(): FetchResult? {
- val albums = Sort.ByName(true)
- .sortAlbums(artist.albums)
- val results = albums.mapAtMost(4) { album ->
- fetchArt(context, album)
- }
+ val albums = Sort.ByName(true).sortAlbums(artist.albums)
+ val results = albums.mapAtMost(4) { album -> fetchArt(context, album) }
return createMosaic(context, results, size)
}
@@ -97,7 +91,8 @@ class ArtistImageFetcher private constructor(
* Fetcher that fetches the image for a [Genre]
* @author OxygenCobalt
*/
-class GenreImageFetcher private constructor(
+class GenreImageFetcher
+private constructor(
private val context: Context,
private val size: Size,
private val genre: Genre,
@@ -105,9 +100,7 @@ class GenreImageFetcher private constructor(
override suspend fun fetch(): FetchResult? {
// We don't need to sort here, as the way we
val albums = genre.songs.groupBy { it.album }.keys
- val results = albums.mapAtMost(4) { album ->
- fetchArt(context, album)
- }
+ val results = albums.mapAtMost(4) { album -> fetchArt(context, album) }
return createMosaic(context, results, size)
}
@@ -120,10 +113,13 @@ class GenreImageFetcher private constructor(
}
/**
- * Map at most [n] items from a collection. [transform] is called for each item that is eligible.
- * If null is returned, then that item will be skipped.
+ * Map at most [n] items from a collection. [transform] is called for each item that is eligible. If
+ * null is returned, then that item will be skipped.
*/
-private inline fun Collection.mapAtMost(n: Int, transform: (T) -> R?): List {
+private inline fun Collection.mapAtMost(
+ n: Int,
+ transform: (T) -> R?
+): List {
val until = min(size, n)
val out = mutableListOf()
@@ -132,9 +128,7 @@ private inline fun Collection.mapAtMost(n: Int, transform:
break
}
- transform(item)?.let {
- out.add(it)
- }
+ transform(item)?.let { out.add(it) }
}
return out
diff --git a/app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt b/app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt
index bcc7e1902..0b9c58904 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/MusicKeyer.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.coil
import coil.key.Keyer
@@ -5,9 +22,7 @@ import coil.request.Options
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
-/**
- * A basic keyer for music data.
- */
+/** A basic keyer for music data. */
class MusicKeyer : Keyer {
override fun key(data: Music, options: Options): String {
return if (data is Song) {
diff --git a/app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt b/app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt
index 49ff7f46f..32d291179 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/RoundableImageView.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.coil
import android.content.Context
@@ -11,24 +28,24 @@ import org.oxycblt.auxio.util.getColorSafe
import org.oxycblt.auxio.util.stateList
/**
- * An [AppCompatImageView] that applies the specified cornerRadius attribute if the user
- * has enabled the "Round album covers" option. We don't round album covers by default as
- * it desecrates album artwork, but if the user desires it we do have an option to enable it.
+ * An [AppCompatImageView] that applies the specified cornerRadius attribute if the user has enabled
+ * the "Round album covers" option. We don't round album covers by default as it desecrates album
+ * artwork, but if the user desires it we do have an option to enable it.
*/
-class RoundableImageView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : AppCompatImageView(context, attrs, defStyleAttr) {
+class RoundableImageView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ AppCompatImageView(context, attrs, defStyleAttr) {
init {
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.RoundableImageView)
val cornerRadius = styledAttrs.getDimension(R.styleable.RoundableImageView_cornerRadius, 0f)
styledAttrs.recycle()
- background = MaterialShapeDrawable().apply {
- setCornerSize(cornerRadius)
- fillColor = context.getColorSafe(android.R.color.transparent).stateList
- }
+ background =
+ MaterialShapeDrawable().apply {
+ setCornerSize(cornerRadius)
+ fillColor = context.getColorSafe(android.R.color.transparent).stateList
+ }
}
override fun onAttachedToWindow() {
diff --git a/app/src/main/java/org/oxycblt/auxio/coil/SquareFrameTransform.kt b/app/src/main/java/org/oxycblt/auxio/coil/SquareFrameTransform.kt
index 1ef87f18c..c7294d1a6 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/SquareFrameTransform.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/SquareFrameTransform.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.coil
import android.graphics.Bitmap
@@ -7,8 +24,8 @@ import coil.transform.Transformation
import kotlin.math.min
/**
- * A transformation that performs a center crop-style transformation on an image, however unlike
- * the actual ScaleType, this isn't affected by any hacks we do with ImageView itself.
+ * A transformation that performs a center crop-style transformation on an image, however unlike the
+ * actual ScaleType, this isn't affected by any hacks we do with ImageView itself.
* @author OxygenCobalt
*/
class SquareFrameTransform : Transformation {
@@ -29,12 +46,7 @@ class SquareFrameTransform : Transformation {
if (dstSize != wantedWidth || dstSize != wantedHeight) {
// Desired size differs from the cropped size, resize the bitmap.
- return Bitmap.createScaledBitmap(
- dst,
- wantedWidth,
- wantedHeight,
- true
- )
+ return Bitmap.createScaledBitmap(dst, wantedWidth, wantedHeight, true)
}
return dst
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
index 44d940065..c08de53c2 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AlbumDetailFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail
import android.content.Context
@@ -58,11 +57,12 @@ class AlbumDetailFragment : DetailFragment() {
detailModel.setAlbum(args.albumId)
val binding = FragmentDetailBinding.inflate(layoutInflater)
- val detailAdapter = AlbumDetailAdapter(
- playbackModel, detailModel,
- doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
- doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ALBUM) }
- )
+ val detailAdapter =
+ AlbumDetailAdapter(
+ playbackModel,
+ detailModel,
+ doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
+ doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ALBUM) })
// --- UI SETUP ---
@@ -75,13 +75,11 @@ class AlbumDetailFragment : DetailFragment() {
requireContext().showToast(R.string.lbl_queue_added)
true
}
-
R.id.action_queue_add -> {
playbackModel.addToQueue(detailModel.curAlbum.value!!)
requireContext().showToast(R.string.lbl_queue_added)
true
}
-
else -> false
}
}
@@ -95,15 +93,11 @@ class AlbumDetailFragment : DetailFragment() {
// -- DETAILVIEWMODEL SETUP ---
- detailModel.albumData.observe(viewLifecycleOwner) { data ->
- detailAdapter.submitList(data)
- }
+ detailModel.albumData.observe(viewLifecycleOwner) { data -> detailAdapter.submitList(data) }
detailModel.showMenu.observe(viewLifecycleOwner) { config ->
if (config != null) {
- showMenu(config) { id ->
- id == R.id.option_sort_asc
- }
+ showMenu(config) { id -> id == R.id.option_sort_asc }
}
}
@@ -118,9 +112,8 @@ class AlbumDetailFragment : DetailFragment() {
detailModel.finishNavToItem()
} else {
logD("Navigating to another album")
- findNavController().navigate(
- AlbumDetailFragmentDirections.actionShowAlbum(item.album.id)
- )
+ findNavController()
+ .navigate(AlbumDetailFragmentDirections.actionShowAlbum(item.album.id))
}
}
@@ -133,20 +126,17 @@ class AlbumDetailFragment : DetailFragment() {
detailModel.finishNavToItem()
} else {
logD("Navigating to another album")
- findNavController().navigate(
- AlbumDetailFragmentDirections.actionShowAlbum(item.id)
- )
+ findNavController()
+ .navigate(AlbumDetailFragmentDirections.actionShowAlbum(item.id))
}
}
// Always launch a new ArtistDetailFragment.
is Artist -> {
logD("Navigating to another artist")
- findNavController().navigate(
- AlbumDetailFragmentDirections.actionShowArtist(item.id)
- )
+ findNavController()
+ .navigate(AlbumDetailFragmentDirections.actionShowArtist(item.id))
}
-
null -> {}
else -> logW("Unsupported navigation item ${item::class.java}")
}
@@ -158,8 +148,7 @@ class AlbumDetailFragment : DetailFragment() {
updateQueueActions(song, binding)
if (playbackModel.playbackMode.value == PlaybackMode.IN_ALBUM &&
- playbackModel.parent.value?.id == detailModel.curAlbum.value!!.id
- ) {
+ playbackModel.parent.value?.id == detailModel.curAlbum.value!!.id) {
detailAdapter.highlightSong(song, binding.detailRecycler)
} else {
// Clear the ViewHolders if the mode isn't ALL_SONGS
@@ -172,9 +161,7 @@ class AlbumDetailFragment : DetailFragment() {
return binding.root
}
- /**
- * Updates the queue actions when
- */
+ /** Updates the queue actions when */
private fun updateQueueActions(song: Song?, binding: FragmentDetailBinding) {
for (item in binding.detailToolbar.menu.children) {
if (item.itemId == R.id.action_play_next || item.itemId == R.id.action_queue_add) {
@@ -183,9 +170,7 @@ class AlbumDetailFragment : DetailFragment() {
}
}
- /**
- * Scroll to an song using its [id].
- */
+ /** Scroll to an song using its [id]. */
private fun scrollToItem(
id: Long,
binding: FragmentDetailBinding,
@@ -198,8 +183,7 @@ class AlbumDetailFragment : DetailFragment() {
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
@@ -210,13 +194,11 @@ class AlbumDetailFragment : DetailFragment() {
}
/**
- * [LinearSmoothScroller] subclass that centers the item on the screen instead of
- * snapping to the top or bottom.
+ * [LinearSmoothScroller] subclass that centers the item on the screen instead of snapping to
+ * the top or bottom.
*/
- private class CenterSmoothScroller(
- context: Context,
- target: Int
- ) : LinearSmoothScroller(context) {
+ private class CenterSmoothScroller(context: Context, target: Int) :
+ LinearSmoothScroller(context) {
init {
targetPosition = target
}
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
index f908d2fe5..778cefeef 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * ArtistDetailFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail
import android.os.Bundle
@@ -53,24 +52,19 @@ class ArtistDetailFragment : DetailFragment() {
detailModel.setArtist(args.artistId)
val binding = FragmentDetailBinding.inflate(layoutInflater)
- val detailAdapter = ArtistDetailAdapter(
- playbackModel,
- doOnClick = { data ->
- if (!detailModel.isNavigating) {
- detailModel.setNavigating(true)
+ val detailAdapter =
+ ArtistDetailAdapter(
+ playbackModel,
+ doOnClick = { data ->
+ if (!detailModel.isNavigating) {
+ detailModel.setNavigating(true)
- findNavController().navigate(
- ArtistDetailFragmentDirections.actionShowAlbum(data.id)
- )
- }
- },
- doOnSongClick = { data ->
- playbackModel.playSong(data, PlaybackMode.IN_ARTIST)
- },
- doOnLongClick = { view, data ->
- newMenu(view, data, ActionMenu.FLAG_IN_ARTIST)
- }
- )
+ findNavController()
+ .navigate(ArtistDetailFragmentDirections.actionShowAlbum(data.id))
+ }
+ },
+ doOnSongClick = { data -> playbackModel.playSong(data, PlaybackMode.IN_ARTIST) },
+ doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ARTIST) })
// --- UI SETUP ---
@@ -91,9 +85,7 @@ class ArtistDetailFragment : DetailFragment() {
detailModel.showMenu.observe(viewLifecycleOwner) { config ->
if (config != null) {
- showMenu(config) { id ->
- id != R.id.option_sort_artist
- }
+ showMenu(config) { id -> id != R.id.option_sort_artist }
}
}
@@ -106,26 +98,20 @@ class ArtistDetailFragment : DetailFragment() {
detailModel.finishNavToItem()
} else {
logD("Navigating to another artist")
- findNavController().navigate(
- ArtistDetailFragmentDirections.actionShowArtist(item.id)
- )
+ findNavController()
+ .navigate(ArtistDetailFragmentDirections.actionShowArtist(item.id))
}
}
-
is Album -> {
logD("Navigating to another album")
- findNavController().navigate(
- ArtistDetailFragmentDirections.actionShowAlbum(item.id)
- )
+ findNavController()
+ .navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.id))
}
-
is Song -> {
logD("Navigating to another album")
- findNavController().navigate(
- ArtistDetailFragmentDirections.actionShowAlbum(item.album.id)
- )
+ findNavController()
+ .navigate(ArtistDetailFragmentDirections.actionShowAlbum(item.album.id))
}
-
null -> {}
else -> logW("Unsupported navigation item ${item::class.java}")
}
@@ -143,8 +129,7 @@ class ArtistDetailFragment : DetailFragment() {
// Highlight songs if they are being played
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.playbackMode.value == PlaybackMode.IN_ARTIST &&
- playbackModel.parent.value?.id == detailModel.curArtist.value?.id
- ) {
+ playbackModel.parent.value?.id == detailModel.curArtist.value?.id) {
detailAdapter.highlightSong(song, binding.detailRecycler)
} else {
// Clear the ViewHolders if the mode isn't ALL_SONGS
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt
index 9aaf16b6f..b89548f56 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.detail
import android.animation.ValueAnimator
@@ -12,24 +29,23 @@ 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.Exception
import org.oxycblt.auxio.R
import org.oxycblt.auxio.ui.EdgeAppBarLayout
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logTraceOrThrow
-import java.lang.Exception
/**
* An [EdgeAppBarLayout] variant that also shows the name of the toolbar whenever the detail
* recyclerview is scrolled beyond it's first item (a.k.a the header). This is used instead of
- * CollapsingToolbarLayout since that thing is a mess with crippling bugs and state issues.
- * This just works.
+ * CollapsingToolbarLayout since that thing is a mess with crippling bugs and state issues. This
+ * just works.
* @author OxygenCobalt
*/
-class DetailAppBarLayout @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : EdgeAppBarLayout(context, attrs, defStyleAttr) {
+class DetailAppBarLayout
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ EdgeAppBarLayout(context, attrs, defStyleAttr) {
private var mTitleView: AppCompatTextView? = null
private var mRecycler: RecyclerView? = null
@@ -50,16 +66,17 @@ class DetailAppBarLayout @JvmOverloads constructor(
val toolbar = findViewById(R.id.detail_toolbar)
// Reflect to get the actual title view to do transformations on
- val newTitleView = try {
- Toolbar::class.java.getDeclaredField("mTitleTextView").run {
- isAccessible = true
- get(toolbar) as AppCompatTextView
+ val newTitleView =
+ try {
+ Toolbar::class.java.getDeclaredField("mTitleTextView").run {
+ isAccessible = true
+ get(toolbar) as AppCompatTextView
+ }
+ } catch (e: Exception) {
+ logE("Could not get toolbar title view (likely an internal code change)")
+ e.logTraceOrThrow()
+ return null
}
- } catch (e: Exception) {
- logE("Could not get toolbar title view (likely an internal code change)")
- e.logTraceOrThrow()
- return null
- }
newTitleView.alpha = 0f
mTitleView = newTitleView
@@ -103,21 +120,21 @@ class DetailAppBarLayout @JvmOverloads constructor(
if (titleView?.alpha == to) return
- mTitleAnimator = ValueAnimator.ofFloat(from, to).apply {
- addUpdateListener {
- titleView?.alpha = it.animatedValue as Float
+ mTitleAnimator =
+ ValueAnimator.ofFloat(from, to).apply {
+ addUpdateListener { titleView?.alpha = it.animatedValue as Float }
+
+ duration =
+ resources.getInteger(R.integer.detail_app_bar_title_anim_duration).toLong()
+
+ start()
}
-
- duration = resources.getInteger(R.integer.detail_app_bar_title_anim_duration).toLong()
-
- start()
- }
}
- class Behavior @JvmOverloads constructor(
- context: Context? = null,
- attrs: AttributeSet? = null
- ) : AppBarLayout.Behavior(context, attrs) {
+ class Behavior
+ @JvmOverloads
+ constructor(context: Context? = null, attrs: AttributeSet? = null) :
+ AppBarLayout.Behavior(context, attrs) {
override fun onNestedPreScroll(
coordinatorLayout: CoordinatorLayout,
child: AppBarLayout,
@@ -132,8 +149,8 @@ class DetailAppBarLayout @JvmOverloads constructor(
val appBar = child as DetailAppBarLayout
val recycler = appBar.findRecyclerView()
- val showTitle = (recycler.layoutManager as LinearLayoutManager)
- .findFirstVisibleItemPosition() > 0
+ val showTitle =
+ (recycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() > 0
appBar.setTitleVisibility(showTitle)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt
index a3a5c643b..bc66bca2a 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * DetailFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail
import androidx.annotation.MenuRes
@@ -70,21 +69,15 @@ abstract class DetailFragment : Fragment() {
inflateMenu(menuId)
}
- setNavigationOnClickListener {
- findNavController().navigateUp()
- }
+ setNavigationOnClickListener { findNavController().navigateUp() }
onMenuClick?.let { onClick ->
- setOnMenuItemClickListener { item ->
- onClick(item.itemId)
- }
+ setOnMenuItemClickListener { item -> onClick(item.itemId) }
}
}
}
- /**
- * Shortcut method for recyclerview setup
- */
+ /** Shortcut method for recyclerview setup */
protected fun setupRecycler(
binding: FragmentDetailBinding,
detailAdapter: RecyclerView.Adapter,
@@ -99,10 +92,14 @@ abstract class DetailFragment : Fragment() {
/**
* Shortcut method for spinning up the sorting [PopupMenu]
- * @param config The initial configuration to apply to the menu. This is provided by [DetailViewModel.showMenu].
+ * @param config The initial configuration to apply to the menu. This is provided by
+ * [DetailViewModel.showMenu].
* @param showItem Which menu items to keep
*/
- protected fun showMenu(config: DetailViewModel.MenuConfig, showItem: ((Int) -> Boolean)? = null) {
+ protected fun showMenu(
+ config: DetailViewModel.MenuConfig,
+ showItem: ((Int) -> Boolean)? = null
+ ) {
logD("Launching menu [$config]")
PopupMenu(config.anchor.context, config.anchor).apply {
@@ -120,9 +117,7 @@ abstract class DetailFragment : Fragment() {
true
}
- setOnDismissListener {
- detailModel.finishShowMenu(null)
- }
+ setOnDismissListener { detailModel.finishShowMenu(null) }
if (showItem != null) {
for (item in menu.children) {
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt
index 31a41dc57..f46afc5f7 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * DetailViewModel.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail
import android.view.View
@@ -47,22 +46,26 @@ class DetailViewModel : ViewModel() {
// --- CURRENT VALUES ---
private val mCurGenre = MutableLiveData()
- val curGenre: LiveData get() = mCurGenre
+ val curGenre: LiveData
+ get() = mCurGenre
private val mGenreData = MutableLiveData(listOf- ())
val genreData: LiveData
> = mGenreData
private val mCurArtist = MutableLiveData()
- val curArtist: LiveData get() = mCurArtist
+ val curArtist: LiveData
+ get() = mCurArtist
private val mArtistData = MutableLiveData(listOf- ())
val artistData: LiveData
> = mArtistData
private val mCurAlbum = MutableLiveData()
- val curAlbum: LiveData get() = mCurAlbum
+ val curAlbum: LiveData
+ get() = mCurAlbum
private val mAlbumData = MutableLiveData(listOf- ())
- val albumData: LiveData
> get() = mAlbumData
+ val albumData: LiveData>
+ get() = mAlbumData
data class MenuConfig(val anchor: View, val sortMode: Sort)
@@ -72,7 +75,8 @@ class DetailViewModel : ViewModel() {
private val mNavToItem = MutableLiveData- ()
/** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */
- val navToItem: LiveData
- get() = mNavToItem
+ val navToItem: LiveData
-
+ get() = mNavToItem
var isNavigating = false
private set
@@ -101,10 +105,7 @@ class DetailViewModel : ViewModel() {
refreshAlbumData()
}
- /**
- * Mark that the menu process is done with the new [Sort].
- * Pass null if there was no change.
- */
+ /** Mark that the menu process is done with the new [Sort]. Pass null if there was no change. */
fun finishShowMenu(newMode: Sort?) {
mShowMenu.value = null
@@ -130,23 +131,17 @@ class DetailViewModel : ViewModel() {
currentMenuContext = null
}
- /**
- * Navigate to an item, whether a song/album/artist
- */
+ /** Navigate to an item, whether a song/album/artist */
fun navToItem(item: Item) {
mNavToItem.value = item
}
- /**
- * Mark that the navigation process is done.
- */
+ /** Mark that the navigation process is done. */
fun finishNavToItem() {
mNavToItem.value = null
}
- /**
- * Update the current navigation status to [isNavigating]
- */
+ /** Update the current navigation status to [isNavigating] */
fun setNavigating(navigating: Boolean) {
isNavigating = navigating
}
@@ -165,9 +160,7 @@ class DetailViewModel : ViewModel() {
onClick = { view ->
currentMenuContext = DisplayMode.SHOW_GENRES
mShowMenu.value = MenuConfig(view, settingsManager.detailGenreSort)
- }
- )
- )
+ }))
data.addAll(settingsManager.detailGenreSort.sortGenre(curGenre.value!!))
@@ -179,12 +172,7 @@ class DetailViewModel : ViewModel() {
val artist = requireNotNull(curArtist.value)
val data = mutableListOf
- (artist)
- data.add(
- Header(
- id = -2,
- string = R.string.lbl_albums
- )
- )
+ data.add(Header(id = -2, string = R.string.lbl_albums))
data.addAll(Sort.ByYear(false).sortAlbums(artist.albums))
@@ -197,9 +185,7 @@ class DetailViewModel : ViewModel() {
onClick = { view ->
currentMenuContext = DisplayMode.SHOW_ARTISTS
mShowMenu.value = MenuConfig(view, settingsManager.detailArtistSort)
- }
- )
- )
+ }))
data.addAll(settingsManager.detailArtistSort.sortArtist(artist))
@@ -220,9 +206,7 @@ class DetailViewModel : ViewModel() {
onClick = { view ->
currentMenuContext = DisplayMode.SHOW_ALBUMS
mShowMenu.value = MenuConfig(view, settingsManager.detailAlbumSort)
- }
- )
- )
+ }))
data.addAll(settingsManager.detailAlbumSort.sortAlbum(curAlbum.value!!))
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
index 96bc663ac..583341066 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * GenreDetailFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail
import android.os.Bundle
@@ -53,15 +52,11 @@ class GenreDetailFragment : DetailFragment() {
detailModel.setGenre(args.genreId)
val binding = FragmentDetailBinding.inflate(inflater)
- val detailAdapter = GenreDetailAdapter(
- playbackModel,
- doOnClick = { song ->
- playbackModel.playSong(song, PlaybackMode.IN_GENRE)
- },
- doOnLongClick = { view, data ->
- newMenu(view, data, ActionMenu.FLAG_IN_GENRE)
- }
- )
+ val detailAdapter =
+ GenreDetailAdapter(
+ playbackModel,
+ doOnClick = { song -> playbackModel.playSong(song, PlaybackMode.IN_GENRE) },
+ doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_GENRE) })
// --- UI SETUP ---
@@ -75,34 +70,26 @@ class GenreDetailFragment : DetailFragment() {
// --- DETAILVIEWMODEL SETUP ---
- detailModel.genreData.observe(viewLifecycleOwner) { data ->
- detailAdapter.submitList(data)
- }
+ detailModel.genreData.observe(viewLifecycleOwner) { data -> detailAdapter.submitList(data) }
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
when (item) {
// All items will launch new detail fragments.
is Artist -> {
logD("Navigating to another artist")
- findNavController().navigate(
- GenreDetailFragmentDirections.actionShowArtist(item.id)
- )
+ findNavController()
+ .navigate(GenreDetailFragmentDirections.actionShowArtist(item.id))
}
-
is Album -> {
logD("Navigating to another album")
- findNavController().navigate(
- GenreDetailFragmentDirections.actionShowAlbum(item.id)
- )
+ findNavController()
+ .navigate(GenreDetailFragmentDirections.actionShowAlbum(item.id))
}
-
is Song -> {
logD("Navigating to another song")
- findNavController().navigate(
- GenreDetailFragmentDirections.actionShowAlbum(item.album.id)
- )
+ findNavController()
+ .navigate(GenreDetailFragmentDirections.actionShowAlbum(item.album.id))
}
-
null -> {}
else -> logW("Unsupported navigation command ${item::class.java}")
}
@@ -112,8 +99,7 @@ class GenreDetailFragment : DetailFragment() {
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.playbackMode.value == PlaybackMode.IN_GENRE &&
- playbackModel.parent.value?.id == detailModel.curGenre.value!!.id
- ) {
+ playbackModel.parent.value?.id == detailModel.curGenre.value!!.id) {
detailAdapter.highlightSong(song, binding.detailRecycler)
} else {
// Clear the ViewHolders if the mode isn't ALL_SONGS
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt
index f58733277..004cb456c 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AlbumDetailAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail.recycler
import android.view.View
@@ -63,16 +62,11 @@ class AlbumDetailAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
- ALBUM_DETAIL_ITEM_TYPE -> AlbumDetailViewHolder(
- ItemDetailBinding.inflate(parent.context.inflater)
- )
-
- ALBUM_SONG_ITEM_TYPE -> AlbumSongViewHolder(
- ItemAlbumSongBinding.inflate(parent.context.inflater)
- )
-
+ ALBUM_DETAIL_ITEM_TYPE ->
+ AlbumDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
+ ALBUM_SONG_ITEM_TYPE ->
+ AlbumSongViewHolder(ItemAlbumSongBinding.inflate(parent.context.inflater))
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
-
else -> error("Invalid ViewHolder item type $viewType")
}
}
@@ -84,8 +78,7 @@ class AlbumDetailAdapter(
is Album -> (holder as AlbumDetailViewHolder).bind(item)
is Song -> (holder as AlbumSongViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
- else -> {
- }
+ else -> {}
}
if (holder is Highlightable) {
@@ -114,9 +107,7 @@ class AlbumDetailAdapter(
if (song != null) {
// Use existing data instead of having to re-sort it.
- val pos = currentList.indexOfFirst { item ->
- item.id == song.id && item is Song
- }
+ val pos = currentList.indexOfFirst { item -> item.id == song.id && item is Song }
// Check if the ViewHolder for this song is visible, if it is then highlight it.
// If the ViewHolder is not visible, then the adapter should take care of it if
@@ -130,9 +121,8 @@ class AlbumDetailAdapter(
}
}
- inner class AlbumDetailViewHolder(
- private val binding: ItemDetailBinding
- ) : BaseViewHolder(binding) {
+ inner class AlbumDetailViewHolder(private val binding: ItemDetailBinding) :
+ BaseViewHolder(binding) {
override fun onBind(data: Album) {
binding.detailCover.apply {
@@ -144,27 +134,21 @@ class AlbumDetailAdapter(
binding.detailSubhead.apply {
text = data.artist.resolvedName
- setOnClickListener {
- detailModel.navToItem(data.artist)
- }
+ setOnClickListener { detailModel.navToItem(data.artist) }
}
binding.detailInfo.apply {
- text = context.getString(
- R.string.fmt_three,
- data.year?.toString() ?: context.getString(R.string.def_date),
- context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size),
- data.totalDuration
- )
+ text =
+ context.getString(
+ R.string.fmt_three,
+ data.year?.toString() ?: context.getString(R.string.def_date),
+ context.getPluralSafe(R.plurals.fmt_song_count, data.songs.size),
+ data.totalDuration)
}
- binding.detailPlayButton.setOnClickListener {
- playbackModel.playAlbum(data, false)
- }
+ binding.detailPlayButton.setOnClickListener { playbackModel.playAlbum(data, false) }
- binding.detailShuffleButton.setOnClickListener {
- playbackModel.playAlbum(data, true)
- }
+ binding.detailShuffleButton.setOnClickListener { playbackModel.playAlbum(data, true) }
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt
index e80eb2ce2..499114da3 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/ArtistDetailAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * ArtistDetailAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail.recycler
import android.view.View
@@ -70,22 +69,14 @@ class ArtistDetailAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
- ARTIST_DETAIL_ITEM_TYPE -> ArtistDetailViewHolder(
- ItemDetailBinding.inflate(parent.context.inflater)
- )
-
- ARTIST_ALBUM_ITEM_TYPE -> ArtistAlbumViewHolder(
- ItemArtistAlbumBinding.inflate(parent.context.inflater)
- )
-
- ARTIST_SONG_ITEM_TYPE -> ArtistSongViewHolder(
- ItemArtistSongBinding.inflate(parent.context.inflater)
- )
-
+ ARTIST_DETAIL_ITEM_TYPE ->
+ ArtistDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
+ ARTIST_ALBUM_ITEM_TYPE ->
+ ArtistAlbumViewHolder(ItemArtistAlbumBinding.inflate(parent.context.inflater))
+ ARTIST_SONG_ITEM_TYPE ->
+ ArtistSongViewHolder(ItemArtistSongBinding.inflate(parent.context.inflater))
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
-
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
-
else -> error("Invalid ViewHolder item type $viewType")
}
}
@@ -99,8 +90,7 @@ class ArtistDetailAdapter(
is Song -> (holder as ArtistSongViewHolder).bind(item)
is Header -> (holder as HeaderViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
- else -> {
- }
+ else -> {}
}
if (holder is Highlightable) {
@@ -133,9 +123,7 @@ class ArtistDetailAdapter(
if (album != null) {
// Use existing data instead of having to re-sort it.
- val pos = currentList.indexOfFirst { item ->
- item.id == album.id && item is Album
- }
+ val pos = currentList.indexOfFirst { item -> item.id == album.id && item is Album }
// Check if the ViewHolder if this album is visible, and highlight it if so.
recycler.layoutManager?.findViewByPosition(pos)?.let { child ->
@@ -163,9 +151,7 @@ class ArtistDetailAdapter(
if (song != null) {
// Use existing data instead of having to re-sort it.
// We also have to account for the album count when searching for the ViewHolder.
- val pos = currentList.indexOfFirst { item ->
- item.id == song.id && item is Song
- }
+ val pos = currentList.indexOfFirst { item -> item.id == song.id && item is Song }
// Check if the ViewHolder for this song is visible, if it is then highlight it.
// If the ViewHolder is not visible, then the adapter should take care of it if
@@ -179,39 +165,35 @@ class ArtistDetailAdapter(
}
}
- inner class ArtistDetailViewHolder(
- private val binding: ItemDetailBinding
- ) : BaseViewHolder(binding) {
+ inner class ArtistDetailViewHolder(private val binding: ItemDetailBinding) :
+ BaseViewHolder(binding) {
override fun onBind(data: Artist) {
val context = binding.root.context
binding.detailCover.apply {
bindArtistImage(data)
- contentDescription = context.getString(
- R.string.desc_artist_image,
- data.resolvedName
- )
+ contentDescription =
+ context.getString(R.string.desc_artist_image, data.resolvedName)
}
binding.detailName.text = data.resolvedName
// Get the genre that corresponds to the most songs in this artist, which would be
// the most "Prominent" genre.
- binding.detailSubhead.text = data.songs
- .groupBy { it.genre.resolvedName }
- .entries.maxByOrNull { it.value.size }
- ?.key ?: context.getString(R.string.def_genre)
+ binding.detailSubhead.text =
+ data.songs
+ .groupBy { it.genre.resolvedName }
+ .entries
+ .maxByOrNull { it.value.size }
+ ?.key
+ ?: context.getString(R.string.def_genre)
binding.detailInfo.bindArtistInfo(data)
- binding.detailPlayButton.setOnClickListener {
- playbackModel.playArtist(data, false)
- }
+ binding.detailPlayButton.setOnClickListener { playbackModel.playArtist(data, false) }
- binding.detailShuffleButton.setOnClickListener {
- playbackModel.playArtist(data, true)
- }
+ binding.detailShuffleButton.setOnClickListener { playbackModel.playArtist(data, true) }
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt
index 11b2affbf..8704bf4c8 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/GenreDetailAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * GenreDetailAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail.recycler
import android.view.View
@@ -60,16 +59,13 @@ class GenreDetailAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
- GENRE_DETAIL_ITEM_TYPE -> GenreDetailViewHolder(
- ItemDetailBinding.inflate(parent.context.inflater)
- )
-
- GENRE_SONG_ITEM_TYPE -> GenreSongViewHolder(
- ItemGenreSongBinding.inflate(parent.context.inflater),
- )
-
+ GENRE_DETAIL_ITEM_TYPE ->
+ GenreDetailViewHolder(ItemDetailBinding.inflate(parent.context.inflater))
+ GENRE_SONG_ITEM_TYPE ->
+ GenreSongViewHolder(
+ ItemGenreSongBinding.inflate(parent.context.inflater),
+ )
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
-
else -> error("Bad ViewHolder item type $viewType")
}
}
@@ -110,9 +106,7 @@ class GenreDetailAdapter(
if (song != null) {
// Use existing data instead of having to re-sort it.
- val pos = currentList.indexOfFirst { item ->
- item.id == song.id && item is Song
- }
+ val pos = currentList.indexOfFirst { item -> item.id == song.id && item is Song }
// Check if the ViewHolder for this song is visible, if it is then highlight it.
// If the ViewHolder is not visible, then the adapter should take care of it if
@@ -126,31 +120,23 @@ class GenreDetailAdapter(
}
}
- inner class GenreDetailViewHolder(
- private val binding: ItemDetailBinding
- ) : BaseViewHolder(binding) {
+ inner class GenreDetailViewHolder(private val binding: ItemDetailBinding) :
+ BaseViewHolder(binding) {
override fun onBind(data: Genre) {
val context = binding.root.context
binding.detailCover.apply {
bindGenreImage(data)
- contentDescription = context.getString(
- R.string.desc_genre_image,
- data.resolvedName
- )
+ contentDescription = context.getString(R.string.desc_genre_image, data.resolvedName)
}
binding.detailName.text = data.resolvedName
binding.detailSubhead.bindGenreInfo(data)
binding.detailInfo.text = data.totalDuration
- binding.detailPlayButton.setOnClickListener {
- playbackModel.playGenre(data, false)
- }
+ binding.detailPlayButton.setOnClickListener { playbackModel.playGenre(data, false) }
- binding.detailShuffleButton.setOnClickListener {
- playbackModel.playGenre(data, true)
- }
+ binding.detailShuffleButton.setOnClickListener { playbackModel.playGenre(data, true) }
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/Highlightable.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/Highlightable.kt
index 532d6040d..d5b3aec2f 100644
--- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/Highlightable.kt
+++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/Highlightable.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Highlightable.kt is part of Auxio.
*
* 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
@@ -15,12 +14,10 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.detail.recycler
-/**
- * Interface that allows the highlighting of certain ViewHolders
- */
+/** Interface that allows the highlighting of certain ViewHolders */
interface Highlightable {
fun setHighlighted(isHighlighted: Boolean)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt
index e76f5941a..071a32518 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/AdaptiveTabStrategy.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.home
import android.content.Context
@@ -11,10 +28,8 @@ import org.oxycblt.auxio.util.logD
* - On medium screens, use only text
* - On large screens, use text and an icon
*/
-class AdaptiveTabStrategy(
- context: Context,
- private val homeModel: HomeViewModel
-) : TabLayoutMediator.TabConfigurationStrategy {
+class AdaptiveTabStrategy(context: Context, private val homeModel: HomeViewModel) :
+ TabLayoutMediator.TabConfigurationStrategy {
private val width = context.resources.configuration.smallestScreenWidthDp
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
@@ -23,19 +38,15 @@ class AdaptiveTabStrategy(
when {
width < 370 -> {
logD("Using icon-only configuration")
- tab.setIcon(tabMode.icon)
- .setContentDescription(tabMode.string)
+ tab.setIcon(tabMode.icon).setContentDescription(tabMode.string)
}
-
width < 640 -> {
logD("Using text-only configuration")
tab.setText(tabMode.string)
}
-
else -> {
logD("Using icon-and-text configuration")
- tab.setIcon(tabMode.icon)
- .setText(tabMode.string)
+ tab.setIcon(tabMode.icon).setText(tabMode.string)
}
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/home/EdgeFabContainer.kt b/app/src/main/java/org/oxycblt/auxio/home/EdgeFabContainer.kt
index 8828c62f6..6cd476431 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/EdgeFabContainer.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/EdgeFabContainer.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * EdgeFloatingActionButton.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home
import android.content.Context
@@ -30,11 +29,10 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
* A container for a FloatingActionButton that enables edge-to-edge support.
* @author OxygenCobalt
*/
-class EdgeFabContainer @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : FrameLayout(context, attrs, defStyleAttr) {
+class EdgeFabContainer
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ FrameLayout(context, attrs, defStyleAttr) {
init {
clipToPadding = false
}
diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt
index 8a9d6af25..049d46892 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * MainFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home
import android.os.Bundle
@@ -52,11 +51,10 @@ import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.logTraceOrThrow
/**
- * The main "Launching Point" fragment of Auxio, allowing navigation to the detail
- * views for each respective item.
- * @author OxygenCobalt
- * TODO: Make tabs invisible when there is only one
- * TODO: Add duration and song count sorts
+ * The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each
+ * respective item.
+ * @author OxygenCobalt TODO: Make tabs invisible when there is only one TODO: Add duration and song
+ * count sorts
*/
class HomeFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
@@ -83,35 +81,37 @@ class HomeFragment : Fragment() {
logD("Navigating to search")
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
}
-
R.id.action_settings -> {
logD("Navigating to settings")
- parentFragment?.parentFragment?.findNavController()?.navigate(
- MainFragmentDirections.actionShowSettings()
- )
+ parentFragment
+ ?.parentFragment
+ ?.findNavController()
+ ?.navigate(MainFragmentDirections.actionShowSettings())
}
-
R.id.action_about -> {
logD("Navigating to about")
- parentFragment?.parentFragment?.findNavController()?.navigate(
- MainFragmentDirections.actionShowAbout()
- )
+ parentFragment
+ ?.parentFragment
+ ?.findNavController()
+ ?.navigate(MainFragmentDirections.actionShowAbout())
}
-
- R.id.submenu_sorting -> { }
-
+ R.id.submenu_sorting -> {}
R.id.option_sort_asc -> {
item.isChecked = !item.isChecked
- val new = homeModel.getSortForDisplay(homeModel.curTab.value!!)
- .ascending(item.isChecked)
+ val new =
+ homeModel
+ .getSortForDisplay(homeModel.curTab.value!!)
+ .ascending(item.isChecked)
homeModel.updateCurrentSort(new)
}
// Sorting option was selected, mark it as selected and update the mode
else -> {
item.isChecked = true
- val new = homeModel.getSortForDisplay(homeModel.curTab.value!!)
- .assignId(item.itemId)
+ val new =
+ homeModel
+ .getSortForDisplay(homeModel.curTab.value!!)
+ .assignId(item.itemId)
homeModel.updateCurrentSort(requireNotNull(new))
}
}
@@ -129,12 +129,14 @@ class HomeFragment : Fragment() {
// scroll events being registered as horizontal scroll events. Reflect into the
// internal recyclerview and change the touch slope so that touch actions will
// act more as a scroll than as a swipe.
- // Derived from: https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414
+ // Derived from:
+ // https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414
try {
- val recycler = ViewPager2::class.java.getDeclaredField("mRecyclerView").run {
- isAccessible = true
- get(binding.homePager)
- }
+ val recycler =
+ ViewPager2::class.java.getDeclaredField("mRecyclerView").run {
+ isAccessible = true
+ get(binding.homePager)
+ }
RecyclerView::class.java.getDeclaredField("mTouchSlop").apply {
isAccessible = true
@@ -152,18 +154,17 @@ class HomeFragment : Fragment() {
// page transitions.
offscreenPageLimit = homeModel.tabs.size
- registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) = homeModel.updateCurrentTab(position)
- })
+ registerOnPageChangeCallback(
+ object : ViewPager2.OnPageChangeCallback() {
+ override fun onPageSelected(position: Int) =
+ homeModel.updateCurrentTab(position)
+ })
- TabLayoutMediator(
- binding.homeTabs, this, AdaptiveTabStrategy(context, homeModel)
- ).attach()
+ TabLayoutMediator(binding.homeTabs, this, AdaptiveTabStrategy(context, homeModel))
+ .attach()
}
- binding.homeFab.setOnClickListener {
- playbackModel.shuffleAll()
- }
+ binding.homeFab.setOnClickListener { playbackModel.shuffleAll() }
// --- VIEWMODEL SETUP ---
@@ -213,18 +214,12 @@ class HomeFragment : Fragment() {
// the tab changes.
when (tab) {
DisplayMode.SHOW_SONGS -> updateSortMenu(sortItem, tab)
-
- DisplayMode.SHOW_ALBUMS -> updateSortMenu(sortItem, tab) { id ->
- id != R.id.option_sort_album
- }
-
- DisplayMode.SHOW_ARTISTS -> updateSortMenu(sortItem, tab) { id ->
- id == R.id.option_sort_asc
- }
-
- DisplayMode.SHOW_GENRES -> updateSortMenu(sortItem, tab) { id ->
- id == R.id.option_sort_asc
- }
+ DisplayMode.SHOW_ALBUMS ->
+ updateSortMenu(sortItem, tab) { id -> id != R.id.option_sort_album }
+ DisplayMode.SHOW_ARTISTS ->
+ updateSortMenu(sortItem, tab) { id -> id == R.id.option_sort_asc }
+ DisplayMode.SHOW_GENRES ->
+ updateSortMenu(sortItem, tab) { id -> id == R.id.option_sort_asc }
}
binding.homeAppbar.liftOnScrollTargetViewId = tab.viewId
@@ -236,24 +231,19 @@ class HomeFragment : Fragment() {
// This is only here just in case a collapsing toolbar is re-added.
binding.homeAppbar.post {
when (item) {
- is Song -> findNavController().navigate(
- HomeFragmentDirections.actionShowAlbum(item.album.id)
- )
-
- is Album -> findNavController().navigate(
- HomeFragmentDirections.actionShowAlbum(item.id)
- )
-
- is Artist -> findNavController().navigate(
- HomeFragmentDirections.actionShowArtist(item.id)
- )
-
- is Genre -> findNavController().navigate(
- HomeFragmentDirections.actionShowGenre(item.id)
- )
-
- else -> {
- }
+ is Song ->
+ findNavController()
+ .navigate(HomeFragmentDirections.actionShowAlbum(item.album.id))
+ is Album ->
+ findNavController()
+ .navigate(HomeFragmentDirections.actionShowAlbum(item.id))
+ is Artist ->
+ findNavController()
+ .navigate(HomeFragmentDirections.actionShowArtist(item.id))
+ is Genre ->
+ findNavController()
+ .navigate(HomeFragmentDirections.actionShowGenre(item.id))
+ else -> {}
}
}
}
@@ -283,12 +273,14 @@ class HomeFragment : Fragment() {
}
}
- private val DisplayMode.viewId: Int get() = when (this) {
- DisplayMode.SHOW_SONGS -> R.id.home_song_list
- DisplayMode.SHOW_ALBUMS -> R.id.home_album_list
- DisplayMode.SHOW_ARTISTS -> R.id.home_artist_list
- DisplayMode.SHOW_GENRES -> R.id.home_genre_list
- }
+ private val DisplayMode.viewId: Int
+ get() =
+ when (this) {
+ DisplayMode.SHOW_SONGS -> R.id.home_song_list
+ DisplayMode.SHOW_ALBUMS -> R.id.home_album_list
+ DisplayMode.SHOW_ARTISTS -> R.id.home_artist_list
+ DisplayMode.SHOW_GENRES -> R.id.home_genre_list
+ }
private inner class HomePagerAdapter :
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt
index 9089b23f4..f8af2e412 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * HomeViewModel.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home
import androidx.lifecycle.LiveData
@@ -42,31 +41,34 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
private val settingsManager = SettingsManager.getInstance()
private val mSongs = MutableLiveData(listOf())
- val songs: LiveData
> get() = mSongs
+ val songs: LiveData>
+ get() = mSongs
private val mAlbums = MutableLiveData(listOf())
- val albums: LiveData> get() = mAlbums
+ val albums: LiveData>
+ get() = mAlbums
private val mArtists = MutableLiveData(listOf())
- val artists: LiveData> get() = mArtists
+ val artists: LiveData>
+ get() = mArtists
private val mGenres = MutableLiveData(listOf())
- val genres: LiveData> get() = mGenres
+ val genres: LiveData>
+ get() = mGenres
var tabs: List = visibleTabs
private set
/** Internal getter for getting the visible library tabs */
- private val visibleTabs: List get() = settingsManager.libTabs
- .filterIsInstance().map { it.mode }
+ private val visibleTabs: List
+ get() = settingsManager.libTabs.filterIsInstance().map { it.mode }
private val mCurTab = MutableLiveData(tabs[0])
val curTab: LiveData = mCurTab
/**
- * Marker to recreate all library tabs, usually initiated by a settings change.
- * When this flag is set, all tabs (and their respective viewpager fragments) will be
- * recreated from scratch.
+ * Marker to recreate all library tabs, usually initiated by a settings change. When this flag
+ * is set, all tabs (and their respective viewpager fragments) will be recreated from scratch.
*/
private val mRecreateTabs = MutableLiveData(false)
val recreateTabs: LiveData = mRecreateTabs
@@ -86,9 +88,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
}
}
- /**
- * Update the current tab based off of the new ViewPager position.
- */
+ /** Update the current tab based off of the new ViewPager position. */
fun updateCurrentTab(pos: Int) {
logD("Updating current tab to ${tabs[pos]}")
mCurTab.value = tabs[pos]
@@ -107,9 +107,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
}
}
- /**
- * Update the currently displayed item's [Sort].
- */
+ /** Update the currently displayed item's [Sort]. */
fun updateCurrentSort(sort: Sort) {
logD("Updating ${mCurTab.value} sort to $sort")
when (mCurTab.value) {
@@ -117,29 +115,25 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback {
settingsManager.libSongSort = sort
mSongs.value = sort.sortSongs(mSongs.value!!)
}
-
DisplayMode.SHOW_ALBUMS -> {
settingsManager.libAlbumSort = sort
mAlbums.value = sort.sortAlbums(mAlbums.value!!)
}
-
DisplayMode.SHOW_ARTISTS -> {
settingsManager.libArtistSort = sort
mArtists.value = sort.sortParents(mArtists.value!!)
}
-
DisplayMode.SHOW_GENRES -> {
settingsManager.libGenreSort = sort
mGenres.value = sort.sortParents(mGenres.value!!)
}
-
else -> {}
}
}
/**
- * Update the fast scroll state. This is used to control the FAB visibility whenever
- * the user begins to fast scroll.
+ * Update the fast scroll state. This is used to control the FAB visibility whenever the user
+ * begins to fast scroll.
*/
fun updateFastScrolling(scrolling: Boolean) {
mFastScrolling.value = scrolling
diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt
index 1f08b1468..4780775bd 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Md2PopupBackground.java is part of Auxio.
*
* 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
@@ -15,6 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
package org.oxycblt.auxio.home.fastscroll
import android.content.Context
@@ -30,19 +30,18 @@ import android.graphics.drawable.Drawable
import android.os.Build
import android.view.View
import androidx.core.graphics.drawable.DrawableCompat
+import kotlin.math.sqrt
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.getAttrColorSafe
import org.oxycblt.auxio.util.getDimenOffsetSafe
-import kotlin.math.sqrt
/**
- * The custom drawable used as FastScrollRecyclerView's popup background.
- * This is an adaptation from AndroidFastScroll's MD2 theme.
+ * The custom drawable used as FastScrollRecyclerView's popup background. This is an adaptation from
+ * AndroidFastScroll's MD2 theme.
*
- * Attributions as per the Apache 2.0 license:
- * ORIGINAL AUTHOR: Hai Zhang [https://github.com/zhanghai]
- * PROJECT: Android Fast Scroll [https://github.com/zhanghai/AndroidFastScroll]
- * MODIFIER: OxygenCobalt [https://github.com/]
+ * Attributions as per the Apache 2.0 license: ORIGINAL AUTHOR: Hai Zhang
+ * [https://github.com/zhanghai] PROJECT: Android Fast Scroll
+ * [https://github.com/zhanghai/AndroidFastScroll] MODIFIER: OxygenCobalt [https://github.com/]
*
* !!! MODIFICATIONS !!!:
* - Use modified Auxio resources instead of AFS resources
@@ -53,11 +52,12 @@ import kotlin.math.sqrt
* @author Hai Zhang, OxygenCobalt
*/
class FastScrollPopupDrawable(context: Context) : Drawable() {
- private val paint: Paint = Paint().apply {
- isAntiAlias = true
- color = context.getAttrColorSafe(R.attr.colorSecondary)
- style = Paint.Style.FILL
- }
+ private val paint: Paint =
+ Paint().apply {
+ isAntiAlias = true
+ color = context.getAttrColorSafe(R.attr.colorSecondary)
+ style = Paint.Style.FILL
+ }
private val path = Path()
private val matrix = Matrix()
@@ -86,13 +86,14 @@ class FastScrollPopupDrawable(context: Context) : Drawable() {
// Paths don't need to be convex on android Q, but the API was mislabeled and so
// we still have to use this method.
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> outline.setConvexPath(path)
-
- else -> if (!path.isConvex) {
- // The outline path must be convex before Q, but we may run into floating point
- // errors caused by calculations involving sqrt(2) or OEM implementation differences,
- // so in this case we just omit the shadow instead of crashing.
- super.getOutline(outline)
- }
+ else ->
+ if (!path.isConvex) {
+ // The outline path must be convex before Q, but we may run into floating point
+ // errors caused by calculations involving sqrt(2) or OEM implementation
+ // differences,
+ // so in this case we just omit the shadow instead of crashing.
+ super.getOutline(outline)
+ }
}
}
@@ -153,11 +154,15 @@ class FastScrollPopupDrawable(context: Context) : Drawable() {
sweepAngle: Float
) {
path.arcTo(
- centerX - radius, centerY - radius, centerX + radius, centerY + radius,
- startAngle, sweepAngle, false
- )
+ centerX - radius,
+ centerY - radius,
+ centerX + radius,
+ centerY + radius,
+ startAngle,
+ sweepAngle,
+ false)
}
- private val isRtl: Boolean get() =
- DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL
+ private val isRtl: Boolean
+ get() = DrawableCompat.getLayoutDirection(this) == View.LAYOUT_DIRECTION_RTL
}
diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt
index bd08de937..9a6002348 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollRecyclerView.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * FastScrollRecyclerView.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.fastscroll
import android.annotation.SuppressLint
@@ -41,6 +40,7 @@ import androidx.core.widget.TextViewCompat
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.util.canScroll
import org.oxycblt.auxio.util.getAttrColorSafe
@@ -48,20 +48,18 @@ import org.oxycblt.auxio.util.getDimenOffsetSafe
import org.oxycblt.auxio.util.getDimenSizeSafe
import org.oxycblt.auxio.util.getDrawableSafe
import org.oxycblt.auxio.util.systemBarInsetsCompat
-import kotlin.math.abs
/**
* A [RecyclerView] that enables better fast-scrolling. This is fundamentally a implementation of
* Hai Zhang's AndroidFastScroll but slimmed down for Auxio and with a couple of enhancements.
*
- * Attributions as per the Apache 2.0 license:
- * ORIGINAL AUTHOR: Hai Zhang [https://github.com/zhanghai]
- * PROJECT: Android Fast Scroll [https://github.com/zhanghai/AndroidFastScroll]
- * MODIFIER: OxygenCobalt [https://github.com/]
+ * Attributions as per the Apache 2.0 license: ORIGINAL AUTHOR: Hai Zhang
+ * [https://github.com/zhanghai] PROJECT: Android Fast Scroll
+ * [https://github.com/zhanghai/AndroidFastScroll] MODIFIER: OxygenCobalt [https://github.com/]
*
* !!! MODIFICATIONS !!!:
- * - Scroller will no longer show itself on startup or relayouts, which looked unpleasant
- * with multiple views
+ * - Scroller will no longer show itself on startup or relayouts, which looked unpleasant with
+ * multiple views
* - DefaultAnimationHelper and RecyclerViewHelper were merged into the class
* - FastScroller overlay was merged into RecyclerView instance
* - Removed FastScrollerBuilder
@@ -75,17 +73,16 @@ import kotlin.math.abs
*
* @author Hai Zhang, OxygenCobalt
*/
-class FastScrollRecyclerView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : RecyclerView(context, attrs, defStyleAttr) {
+class FastScrollRecyclerView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ RecyclerView(context, attrs, defStyleAttr) {
/** Callback to provide a string to be shown on the popup when an item is passed */
var popupProvider: ((Int) -> String)? = null
/**
- * A listener for when a drag event occurs. The value will be true if a drag has begun,
- * and false if a drag ended.
+ * A listener for when a drag event occurs. The value will be true if a drag has begun, and
+ * false if a drag ended.
*/
var onDragListener: ((Boolean) -> Unit)? = null
@@ -128,35 +125,37 @@ class FastScrollRecyclerView @JvmOverloads constructor(
val thumbDrawable = context.getDrawableSafe(R.drawable.ui_scroll_thumb)
trackView = View(context)
- thumbView = View(context).apply {
- alpha = 0f
- background = thumbDrawable
- }
-
- popupView = AppCompatTextView(context).apply {
- alpha = 0f
- layoutParams = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
- )
-
- minimumWidth = context.getDimenSizeSafe(R.dimen.popup_min_width)
- minimumHeight = context.getDimenSizeSafe(R.dimen.size_btn_large)
-
- (layoutParams as FrameLayout.LayoutParams).apply {
- gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
- marginEnd = context.getDimenOffsetSafe(R.dimen.spacing_small)
+ thumbView =
+ View(context).apply {
+ alpha = 0f
+ background = thumbDrawable
}
- TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge)
- setTextColor(context.getAttrColorSafe(R.attr.colorOnSecondary))
+ popupView =
+ AppCompatTextView(context).apply {
+ alpha = 0f
+ layoutParams =
+ FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- background = FastScrollPopupDrawable(context)
- elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat()
- ellipsize = TextUtils.TruncateAt.MIDDLE
- gravity = Gravity.CENTER
- includeFontPadding = false
- isSingleLine = true
- }
+ minimumWidth = context.getDimenSizeSafe(R.dimen.popup_min_width)
+ minimumHeight = context.getDimenSizeSafe(R.dimen.size_btn_large)
+
+ (layoutParams as FrameLayout.LayoutParams).apply {
+ gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
+ marginEnd = context.getDimenOffsetSafe(R.dimen.spacing_small)
+ }
+
+ TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Auxio_HeadlineLarge)
+ setTextColor(context.getAttrColorSafe(R.attr.colorOnSecondary))
+
+ background = FastScrollPopupDrawable(context)
+ elevation = context.getDimenSizeSafe(R.dimen.elevation_normal).toFloat()
+ ellipsize = TextUtils.TruncateAt.MIDDLE
+ gravity = Gravity.CENTER
+ includeFontPadding = false
+ isSingleLine = true
+ }
thumbWidth = thumbDrawable.intrinsicWidth
thumbHeight = thumbDrawable.intrinsicHeight
@@ -168,33 +167,28 @@ class FastScrollRecyclerView @JvmOverloads constructor(
overlay.add(thumbView)
overlay.add(popupView)
- addItemDecoration(object : ItemDecoration() {
- override fun onDraw(
- canvas: Canvas,
- parent: RecyclerView,
- state: State
- ) {
- onPreDraw()
- }
- })
+ addItemDecoration(
+ object : ItemDecoration() {
+ 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.
- addOnItemTouchListener(object : SimpleOnItemTouchListener() {
- override fun onTouchEvent(
- recyclerView: RecyclerView,
- event: MotionEvent
- ) {
- onItemTouch(event)
- }
+ addOnItemTouchListener(
+ object : SimpleOnItemTouchListener() {
+ override fun onTouchEvent(recyclerView: RecyclerView, event: MotionEvent) {
+ onItemTouch(event)
+ }
- override fun onInterceptTouchEvent(
- recyclerView: RecyclerView,
- event: MotionEvent
- ): Boolean {
- return onItemTouch(event)
- }
- })
+ override fun onInterceptTouchEvent(
+ recyclerView: RecyclerView,
+ event: MotionEvent
+ ): Boolean {
+ return onItemTouch(event)
+ }
+ })
}
// --- RECYCLERVIEW EVENT MANAGEMENT ---
@@ -206,33 +200,34 @@ class FastScrollRecyclerView @JvmOverloads constructor(
thumbView.layoutDirection = layoutDirection
popupView.layoutDirection = layoutDirection
- val trackLeft = if (isRtl) {
- scrollerPadding.left
- } else {
- width - scrollerPadding.right - thumbWidth
- }
+ val trackLeft =
+ if (isRtl) {
+ scrollerPadding.left
+ } else {
+ width - scrollerPadding.right - thumbWidth
+ }
trackView.layout(
- trackLeft, scrollerPadding.top, trackLeft + thumbWidth,
- height - scrollerPadding.bottom
- )
+ trackLeft, scrollerPadding.top, trackLeft + thumbWidth, height - scrollerPadding.bottom)
- val thumbLeft = if (isRtl) {
- scrollerPadding.left
- } else {
- width - scrollerPadding.right - thumbWidth
- }
+ val thumbLeft =
+ if (isRtl) {
+ scrollerPadding.left
+ } else {
+ width - scrollerPadding.right - thumbWidth
+ }
val thumbTop = scrollerPadding.top + thumbOffset
thumbView.layout(thumbLeft, thumbTop, thumbLeft + thumbWidth, thumbTop + thumbHeight)
val firstPos = firstAdapterPos
- val popupText = if (firstPos != NO_POSITION) {
- popupProvider?.invoke(firstPos) ?: ""
- } else {
- ""
- }
+ val popupText =
+ if (firstPos != NO_POSITION) {
+ popupProvider?.invoke(firstPos) ?: ""
+ } else {
+ ""
+ }
popupView.isInvisible = popupText.isEmpty()
@@ -242,58 +237,67 @@ class FastScrollRecyclerView @JvmOverloads constructor(
if (popupView.text != popupText) {
popupView.text = popupText
- val widthMeasureSpec = ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- scrollerPadding.left + scrollerPadding.right + thumbWidth +
- popupLayoutParams.leftMargin + popupLayoutParams.rightMargin,
- popupLayoutParams.width
- )
+ val widthMeasureSpec =
+ ViewGroup.getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ scrollerPadding.left +
+ scrollerPadding.right +
+ thumbWidth +
+ popupLayoutParams.leftMargin +
+ popupLayoutParams.rightMargin,
+ popupLayoutParams.width)
- val heightMeasureSpec = ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
- scrollerPadding.top + scrollerPadding.bottom + popupLayoutParams.topMargin +
- popupLayoutParams.bottomMargin,
- popupLayoutParams.height
- )
+ val heightMeasureSpec =
+ ViewGroup.getChildMeasureSpec(
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
+ scrollerPadding.top +
+ scrollerPadding.bottom +
+ popupLayoutParams.topMargin +
+ popupLayoutParams.bottomMargin,
+ popupLayoutParams.height)
popupView.measure(widthMeasureSpec, heightMeasureSpec)
}
val popupWidth = popupView.measuredWidth
val popupHeight = popupView.measuredHeight
- val popupLeft = if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
- scrollerPadding.left + thumbWidth + popupLayoutParams.leftMargin
- } else {
- width - scrollerPadding.right - thumbWidth - popupLayoutParams.rightMargin - popupWidth
- }
+ val popupLeft =
+ if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ scrollerPadding.left + thumbWidth + popupLayoutParams.leftMargin
+ } else {
+ width -
+ scrollerPadding.right -
+ thumbWidth -
+ popupLayoutParams.rightMargin -
+ popupWidth
+ }
// We handle RTL separately, so it's okay if Gravity.RIGHT is used here
@SuppressLint("RtlHardcoded")
- val popupAnchorY = when (popupLayoutParams.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
- Gravity.CENTER_HORIZONTAL -> popupHeight / 2
- Gravity.RIGHT -> popupHeight
- else -> 0
- }
-
- val thumbAnchorY = when (popupLayoutParams.gravity and Gravity.VERTICAL_GRAVITY_MASK) {
- Gravity.CENTER_VERTICAL -> {
- thumbView.paddingTop + (
- thumbHeight - thumbView.paddingTop - thumbView.paddingBottom
- ) / 2
+ val popupAnchorY =
+ when (popupLayoutParams.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
+ Gravity.CENTER_HORIZONTAL -> popupHeight / 2
+ Gravity.RIGHT -> popupHeight
+ else -> 0
}
- Gravity.BOTTOM -> thumbHeight - thumbView.paddingBottom
- else -> thumbView.paddingTop
- }
- val popupTop = MathUtils.clamp(
- thumbTop + thumbAnchorY - popupAnchorY,
- scrollerPadding.top + popupLayoutParams.topMargin,
- height - scrollerPadding.bottom - popupLayoutParams.bottomMargin - popupHeight
- )
+ val thumbAnchorY =
+ when (popupLayoutParams.gravity and Gravity.VERTICAL_GRAVITY_MASK) {
+ Gravity.CENTER_VERTICAL -> {
+ thumbView.paddingTop +
+ (thumbHeight - thumbView.paddingTop - thumbView.paddingBottom) / 2
+ }
+ Gravity.BOTTOM -> thumbHeight - thumbView.paddingBottom
+ else -> thumbView.paddingTop
+ }
- popupView.layout(
- popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight
- )
+ val popupTop =
+ MathUtils.clamp(
+ thumbTop + thumbAnchorY - popupAnchorY,
+ scrollerPadding.top + popupLayoutParams.topMargin,
+ height - scrollerPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
+
+ popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight)
}
}
@@ -315,9 +319,10 @@ class FastScrollRecyclerView @JvmOverloads constructor(
val bars = insets.systemBarInsetsCompat
updatePadding(
- initialPadding.left, initialPadding.top, initialPadding.right,
- initialPadding.bottom + bars.bottom
- )
+ initialPadding.left,
+ initialPadding.top,
+ initialPadding.right,
+ initialPadding.bottom + bars.bottom)
scrollerPadding.bottom = bars.bottom
@@ -358,24 +363,25 @@ class FastScrollRecyclerView @JvmOverloads constructor(
if (isInViewTouchTarget(thumbView, eventX, eventY)) {
dragStartThumbOffset = thumbOffset
} else {
- dragStartThumbOffset = (eventY - scrollerPadding.top - thumbHeight / 2f).toInt()
+ dragStartThumbOffset =
+ (eventY - scrollerPadding.top - thumbHeight / 2f).toInt()
scrollToThumbOffset(dragStartThumbOffset)
}
setDragging(true)
}
}
-
MotionEvent.ACTION_MOVE -> {
- if (!dragging && isInViewTouchTarget(trackView, downX, downY) &&
- abs(eventY - downY) > touchSlop
- ) {
+ if (!dragging &&
+ isInViewTouchTarget(trackView, downX, downY) &&
+ abs(eventY - downY) > touchSlop) {
if (isInViewTouchTarget(thumbView, downX, downY)) {
dragStartY = lastY
dragStartThumbOffset = thumbOffset
} else {
dragStartY = eventY
- dragStartThumbOffset = (eventY - scrollerPadding.top - thumbHeight / 2f).toInt()
+ dragStartThumbOffset =
+ (eventY - scrollerPadding.top - thumbHeight / 2f).toInt()
scrollToThumbOffset(dragStartThumbOffset)
}
setDragging(true)
@@ -386,7 +392,6 @@ class FastScrollRecyclerView @JvmOverloads constructor(
scrollToThumbOffset(thumbOffset)
}
}
-
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> setDragging(false)
}
@@ -433,9 +438,9 @@ class FastScrollRecyclerView @JvmOverloads constructor(
private fun scrollToThumbOffset(thumbOffset: Int) {
val clampedThumbOffset = MathUtils.clamp(thumbOffset, 0, thumbOffsetRange)
- val scrollOffset = (
- scrollOffsetRange.toLong() * clampedThumbOffset / thumbOffsetRange
- ).toInt() - paddingTop
+ val scrollOffset =
+ (scrollOffsetRange.toLong() * clampedThumbOffset / thumbOffsetRange).toInt() -
+ paddingTop
scrollTo(scrollOffset)
}
@@ -461,7 +466,6 @@ class FastScrollRecyclerView @JvmOverloads constructor(
targetPosition *= mgr.spanCount
mgr.scrollToPositionWithOffset(targetPosition, trueOffset)
}
-
is LinearLayoutManager -> {
mgr.scrollToPositionWithOffset(targetPosition, trueOffset)
}
@@ -538,10 +542,7 @@ class FastScrollRecyclerView @JvmOverloads constructor(
}
private fun animateView(view: View, alpha: Float) {
- view.animate()
- .alpha(alpha)
- .setDuration(ANIM_MILLIS)
- .start()
+ view.animate().alpha(alpha).setDuration(ANIM_MILLIS).start()
}
// --- LAYOUT STATE ---
@@ -601,11 +602,12 @@ class FastScrollRecyclerView @JvmOverloads constructor(
}
private val itemCount: Int
- get() = when (val mgr = layoutManager) {
- is GridLayoutManager -> (mgr.itemCount - 1) / mgr.spanCount + 1
- is LinearLayoutManager -> mgr.itemCount
- else -> 0
- }
+ get() =
+ when (val mgr = layoutManager) {
+ is GridLayoutManager -> (mgr.itemCount - 1) / mgr.spanCount + 1
+ is LinearLayoutManager -> mgr.itemCount
+ else -> 0
+ }
companion object {
private const val ANIM_MILLIS = 150L
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
index 271283e22..e90872c90 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AlbumListFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.list
import android.os.Bundle
@@ -49,14 +48,12 @@ class AlbumListFragment : HomeListFragment() {
binding.lifecycleOwner = viewLifecycleOwner
- val adapter = AlbumAdapter(
- doOnClick = { album ->
- findNavController().navigate(
- HomeFragmentDirections.actionShowAlbum(album.id)
- )
- },
- ::newMenu
- )
+ val adapter =
+ AlbumAdapter(
+ doOnClick = { album ->
+ findNavController().navigate(HomeFragmentDirections.actionShowAlbum(album.id))
+ },
+ ::newMenu)
setupRecycler(R.id.home_album_list, binding, adapter, homeModel.albums)
@@ -70,16 +67,13 @@ class AlbumListFragment : HomeListFragment() {
// Change how we display the popup depending on the mode.
when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) {
// By Name -> Use Name
- is Sort.ByName -> album.name.sliceArticle()
- .first().uppercase()
+ is Sort.ByName -> album.name.sliceArticle().first().uppercase()
// By Artist -> Use Artist Name
- is Sort.ByArtist -> album.artist.resolvedName.sliceArticle()
- .first().uppercase()
+ is Sort.ByArtist -> album.artist.resolvedName.sliceArticle().first().uppercase()
// Year -> Use Full Year
- is Sort.ByYear -> album.year?.toString()
- ?: getString(R.string.def_date)
+ is Sort.ByYear -> album.year?.toString() ?: getString(R.string.def_date)
// Unsupported sort, error gracefully
else -> ""
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
index eb3d96f19..9a3b6620f 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AlbumListFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.list
import android.os.Bundle
@@ -47,14 +46,12 @@ class ArtistListFragment : HomeListFragment() {
binding.lifecycleOwner = viewLifecycleOwner
- val adapter = ArtistAdapter(
- doOnClick = { artist ->
- findNavController().navigate(
- HomeFragmentDirections.actionShowArtist(artist.id)
- )
- },
- ::newMenu
- )
+ val adapter =
+ ArtistAdapter(
+ doOnClick = { artist ->
+ findNavController().navigate(HomeFragmentDirections.actionShowArtist(artist.id))
+ },
+ ::newMenu)
setupRecycler(R.id.home_artist_list, binding, adapter, homeModel.artists)
@@ -63,8 +60,7 @@ class ArtistListFragment : HomeListFragment() {
override val listPopupProvider: (Int) -> String
get() = { idx ->
- homeModel.artists.value!![idx].resolvedName
- .sliceArticle().first().uppercase()
+ homeModel.artists.value!![idx].resolvedName.sliceArticle().first().uppercase()
}
class ArtistAdapter(
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
index 93482d032..70be379ce 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AlbumListFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.list
import android.os.Bundle
@@ -47,14 +46,12 @@ class GenreListFragment : HomeListFragment() {
binding.lifecycleOwner = viewLifecycleOwner
- val adapter = GenreAdapter(
- doOnClick = { Genre ->
- findNavController().navigate(
- HomeFragmentDirections.actionShowGenre(Genre.id)
- )
- },
- ::newMenu
- )
+ val adapter =
+ GenreAdapter(
+ doOnClick = { Genre ->
+ findNavController().navigate(HomeFragmentDirections.actionShowGenre(Genre.id))
+ },
+ ::newMenu)
setupRecycler(R.id.home_genre_list, binding, adapter, homeModel.genres)
@@ -63,8 +60,7 @@ class GenreListFragment : HomeListFragment() {
override val listPopupProvider: (Int) -> String
get() = { idx ->
- homeModel.genres.value!![idx].resolvedName
- .sliceArticle().first().uppercase()
+ homeModel.genres.value!![idx].resolvedName.sliceArticle().first().uppercase()
}
class GenreAdapter(
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt
index 9bca96f19..52749d9bf 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * HomeListFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.list
import android.annotation.SuppressLint
@@ -38,9 +37,7 @@ abstract class HomeListFragment : Fragment() {
protected val homeModel: HomeViewModel by activityViewModels()
protected val playbackModel: PlaybackViewModel by activityViewModels()
- /**
- * The popup provider to use for the fast scroller view.
- */
+ /** The popup provider to use for the fast scroller view. */
abstract val listPopupProvider: (Int) -> String
protected fun setupRecycler(
@@ -56,18 +53,15 @@ abstract class HomeListFragment : Fragment() {
applySpans()
popupProvider = listPopupProvider
- onDragListener = { dragging ->
- homeModel.updateFastScrolling(dragging)
- }
+ onDragListener = { dragging -> homeModel.updateFastScrolling(dragging) }
}
// Make sure that this RecyclerView has data before startup
- homeData.observe(viewLifecycleOwner) { data ->
- homeAdapter.updateData(data)
- }
+ homeData.observe(viewLifecycleOwner) { data -> homeAdapter.updateData(data) }
}
- abstract class HomeAdapter : RecyclerView.Adapter() {
+ abstract class HomeAdapter :
+ RecyclerView.Adapter() {
protected var data = listOf()
@SuppressLint("NotifyDataSetChanged")
diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
index 139d88809..f0068aa15 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SongListFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.list
import android.os.Bundle
@@ -47,12 +46,7 @@ class SongListFragment : HomeListFragment() {
binding.lifecycleOwner = viewLifecycleOwner
- val adapter = SongsAdapter(
- doOnClick = { song ->
- playbackModel.playSong(song)
- },
- ::newMenu
- )
+ val adapter = SongsAdapter(doOnClick = { song -> playbackModel.playSong(song) }, ::newMenu)
setupRecycler(R.id.home_song_list, binding, adapter, homeModel.songs)
@@ -68,21 +62,17 @@ class SongListFragment : HomeListFragment() {
// based off the names of the parent objects and not the child objects.
when (homeModel.getSortForDisplay(DisplayMode.SHOW_SONGS)) {
// Name -> Use name
- is Sort.ByName -> song.name.sliceArticle()
- .first().uppercase()
+ is Sort.ByName -> song.name.sliceArticle().first().uppercase()
// Artist -> Use Artist Name
is Sort.ByArtist ->
- song.album.artist.resolvedName
- .sliceArticle().first().uppercase()
+ song.album.artist.resolvedName.sliceArticle().first().uppercase()
// Album -> Use Album Name
- is Sort.ByAlbum -> song.album.name.sliceArticle()
- .first().uppercase()
+ is Sort.ByAlbum -> song.album.name.sliceArticle().first().uppercase()
// Year -> Use Full Year
- is Sort.ByYear -> song.album.year?.toString()
- ?: getString(R.string.def_date)
+ is Sort.ByYear -> song.album.year?.toString() ?: getString(R.string.def_date)
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt
index 3f8a30686..8c234f5a0 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/Tab.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Tab.kt is part of Auxio.
*
* 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
@@ -15,24 +14,24 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.tabs
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.util.logE
/**
- * A data representation of a library tab.
- * A tab can come in two moves, [Visible] or [Invisible]. Invisibility means that the tab
- * will still be present in the customization menu, but will not be shown on the home UI.
+ * A data representation of a library tab. A tab can come in two moves, [Visible] or [Invisible].
+ * Invisibility means that the tab will still be present in the customization menu, but will not be
+ * shown on the home UI.
*
* Like other IO-bound datatypes in Auxio, tabs are stored in a binary format. However, tabs cannot
* be serialized on their own. Instead, they are saved as a sequence of tabs as shown below:
*
* 0bTAB1_TAB2_TAB3_TAB4_TAB5
*
- * Where TABN is a chunk representing a tab at position N. TAB5 is reserved for playlists.
- * Each chunk in a sequence is represented as:
+ * Where TABN is a chunk representing a tab at position N. TAB5 is reserved for playlists. Each
+ * chunk in a sequence is represented as:
*
* VTTT
*
@@ -49,14 +48,12 @@ sealed class Tab(open val mode: DisplayMode) {
data class Invisible(override val mode: DisplayMode) : Tab(mode)
companion object {
- /** The length a well-formed tab sequence should be **/
+ /** The length a well-formed tab sequence should be */
const val SEQUENCE_LEN = 4
- /** The default tab sequence, represented in integer form **/
+ /** The default tab sequence, represented in integer form */
const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100
- /**
- * Convert an array [tabs] into a sequence of tabs.
- */
+ /** Convert an array [tabs] into a sequence of tabs. */
fun toSequence(tabs: Array): Int {
// Like when deserializing, make sure there are no duplicate tabs for whatever reason.
val distinct = tabs.distinctBy { it.mode }
@@ -65,10 +62,11 @@ sealed class Tab(open val mode: DisplayMode) {
var shift = SEQUENCE_LEN * 4
for (tab in distinct) {
- val bin = when (tab) {
- is Visible -> 1.shl(3) or tab.mode.ordinal
- is Invisible -> tab.mode.ordinal
- }
+ val bin =
+ when (tab) {
+ is Visible -> 1.shl(3) or tab.mode.ordinal
+ is Invisible -> tab.mode.ordinal
+ }
sequence = sequence or bin.shl(shift)
shift -= 4
@@ -77,9 +75,7 @@ sealed class Tab(open val mode: DisplayMode) {
return sequence
}
- /**
- * Convert a [sequence] into an array of tabs.
- */
+ /** Convert a [sequence] into an array of tabs. */
fun fromSequence(sequence: Int): Array? {
val tabs = mutableListOf()
@@ -88,20 +84,22 @@ sealed class Tab(open val mode: DisplayMode) {
for (shift in (0..4 * SEQUENCE_LEN).reversed() step 4) {
val chunk = sequence.shr(shift) and 0b1111
- val mode = when (chunk and 7) {
- 0 -> DisplayMode.SHOW_SONGS
- 1 -> DisplayMode.SHOW_ALBUMS
- 2 -> DisplayMode.SHOW_ARTISTS
- 3 -> DisplayMode.SHOW_GENRES
- else -> continue
- }
+ val mode =
+ when (chunk and 7) {
+ 0 -> DisplayMode.SHOW_SONGS
+ 1 -> DisplayMode.SHOW_ALBUMS
+ 2 -> DisplayMode.SHOW_ARTISTS
+ 3 -> DisplayMode.SHOW_GENRES
+ else -> continue
+ }
// Figure out the visibility
- tabs += if (chunk and 1.shl(3) != 0) {
- Visible(mode)
- } else {
- Invisible(mode)
- }
+ tabs +=
+ if (chunk and 1.shl(3) != 0) {
+ Visible(mode)
+ } else {
+ Invisible(mode)
+ }
}
// Make sure there are no duplicate tabs
diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt
index 401663c45..1a6dfbe31 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * TabAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.tabs
import android.annotation.SuppressLint
@@ -31,7 +30,8 @@ class TabAdapter(
private val getTabs: () -> Array,
private val onTabSwitch: (Tab) -> Unit,
) : RecyclerView.Adapter() {
- private val tabs: Array get() = getTabs()
+ private val tabs: Array
+ get() = getTabs()
override fun getItemCount(): Int = Tab.SEQUENCE_LEN
@@ -43,13 +43,12 @@ class TabAdapter(
holder.bind(tabs[position])
}
- inner class TabViewHolder(
- private val binding: ItemTabBinding
- ) : RecyclerView.ViewHolder(binding.root) {
+ inner class TabViewHolder(private val binding: ItemTabBinding) :
+ RecyclerView.ViewHolder(binding.root) {
init {
- binding.root.layoutParams = RecyclerView.LayoutParams(
- RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT
- )
+ binding.root.layoutParams =
+ RecyclerView.LayoutParams(
+ RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
}
@SuppressLint("ClickableViewAccessibility")
diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt
index 93b478f7d..b303c307b 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * CustomizeListDialog.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.tabs
import android.os.Bundle
@@ -32,8 +31,8 @@ import org.oxycblt.auxio.ui.LifecycleDialog
import org.oxycblt.auxio.util.logD
/**
- * The dialog for customizing library tabs. This dialog does not rely on any specific ViewModel
- * and serializes it's state instead of
+ * The dialog for customizing library tabs. This dialog does not rely on any specific ViewModel and
+ * serializes it's state instead of
* @author OxygenCobalt
*/
class TabCustomizeDialog : LifecycleDialog() {
@@ -58,27 +57,28 @@ class TabCustomizeDialog : LifecycleDialog() {
// Set up adapter & drag callback
val callback = TabDragCallback { pendingTabs }
val helper = ItemTouchHelper(callback)
- val tabAdapter = TabAdapter(
- helper,
- getTabs = { pendingTabs },
- onTabSwitch = { tab ->
- // Don't find the specific tab [Which might be outdated due to the nature
- // of how ViewHolders are bound], but instead simply look for the mode in
- // the list of pending tabs and update that instead.
- val index = pendingTabs.indexOfFirst { it.mode == tab.mode }
- if (index != -1) {
- val curTab = pendingTabs[index]
- logD("Updating tab $curTab to $tab")
- pendingTabs[index] = when (curTab) {
- is Tab.Visible -> Tab.Invisible(curTab.mode)
- is Tab.Invisible -> Tab.Visible(curTab.mode)
+ val tabAdapter =
+ TabAdapter(
+ helper,
+ getTabs = { pendingTabs },
+ onTabSwitch = { tab ->
+ // Don't find the specific tab [Which might be outdated due to the nature
+ // of how ViewHolders are bound], but instead simply look for the mode in
+ // the list of pending tabs and update that instead.
+ val index = pendingTabs.indexOfFirst { it.mode == tab.mode }
+ if (index != -1) {
+ val curTab = pendingTabs[index]
+ logD("Updating tab $curTab to $tab")
+ pendingTabs[index] =
+ when (curTab) {
+ is Tab.Visible -> Tab.Invisible(curTab.mode)
+ is Tab.Invisible -> Tab.Visible(curTab.mode)
+ }
}
- }
- (requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =
- pendingTabs.filterIsInstance().isNotEmpty()
- }
- )
+ (requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
+ .isEnabled = pendingTabs.filterIsInstance().isNotEmpty()
+ })
callback.addTabAdapter(tabAdapter)
diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabDragCallback.kt
index f9de5ac79..e0e071d08 100644
--- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabDragCallback.kt
+++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabDragCallback.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * QueueDragCallback.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.home.tabs
import android.graphics.Canvas
@@ -24,21 +23,18 @@ import androidx.recyclerview.widget.RecyclerView
/**
* A simple [ItemTouchHelper.Callback] that handles dragging items in the tab customization menu.
- * Unlike QueueAdapter's ItemTouchHelper, this one is bare and simple.
- * TODO: Consider unifying the shared behavior between this and QueueDragCallback into a single
- * class.
+ * Unlike QueueAdapter's ItemTouchHelper, this one is bare and simple. TODO: Consider unifying the
+ * shared behavior between this and QueueDragCallback into a single class.
*/
class TabDragCallback(private val getTabs: () -> Array) : ItemTouchHelper.Callback() {
- private val tabs: Array get() = getTabs()
+ private val tabs: Array
+ get() = getTabs()
private lateinit var tabAdapter: TabAdapter
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
- ): Int = makeFlag(
- ItemTouchHelper.ACTION_STATE_DRAG,
- ItemTouchHelper.UP or ItemTouchHelper.DOWN
- )
+ ): Int = makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN)
override fun onChildDraw(
c: Canvas,
@@ -76,8 +72,8 @@ class TabDragCallback(private val getTabs: () -> Array) : ItemTouchHelper.C
override fun isLongPressDragEnabled(): Boolean = false
/**
- * Add the tab adapter to this callback.
- * Done because there's a circular dependency between the two objects
+ * Add the tab adapter to this callback. Done because there's a circular dependency between the
+ * two objects
*/
fun addTabAdapter(adapter: TabAdapter) {
tabAdapter = adapter
diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt
index 13504b1b3..a1ebae9c4 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Models.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music
import android.content.ContentUris
@@ -27,18 +26,15 @@ import androidx.annotation.StringRes
// --- MUSIC MODELS ---
-/**
- * The base for all items in Auxio.
- */
+/** The base for all items in Auxio. */
sealed class Item {
/** A unique ID for this item. ***THIS IS NOT A MEDIASTORE ID!** */
abstract val id: Long
}
/**
- * [Item] variant that represents a music item.
- * TODO: Make name the actual display name and move raw names (including file names) to a new
- * field called rawName.
+ * [Item] variant that represents a music item. TODO: Make name the actual display name and move raw
+ * names (including file names) to a new field called rawName.
*/
sealed class Music : Item() {
/** The raw name of this item. */
@@ -46,21 +42,19 @@ sealed class Music : Item() {
}
/**
- * [Music] variant that denotes that this object is a parent of other data objects, such
- * as an [Album] or [Artist]
+ * [Music] variant that denotes that this object is a parent of other data objects, such as an
+ * [Album] or [Artist]
* @property resolvedName
*/
sealed class MusicParent : Music() {
/**
- * A name resolved from it's raw form to a form suitable to be shown in a ui.
- * Ex. "unknown" would become Unknown Artist, (124) would become its proper genre name, etc.
+ * A name resolved from it's raw form to a form suitable to be shown in a ui. Ex. "unknown"
+ * would become Unknown Artist, (124) would become its proper genre name, etc.
*/
abstract val resolvedName: String
}
-/**
- * The data object for a song.
- */
+/** The data object for a song. */
data class Song(
override val name: String,
/** The file name of this song, excluding the full path. */
@@ -82,57 +76,69 @@ data class Song(
/** Internal field. Do not use. */
val internalMediaStoreAlbumArtistName: String?,
) : Music() {
- override val id: Long get() {
- var result = name.hashCode().toLong()
- result = 31 * result + album.name.hashCode()
- result = 31 * result + album.artist.name.hashCode()
- result = 31 * result + (track ?: 0)
- result = 31 * result + duration.hashCode()
- return result
- }
+ override val id: Long
+ get() {
+ var result = name.hashCode().toLong()
+ result = 31 * result + album.name.hashCode()
+ result = 31 * result + album.artist.name.hashCode()
+ result = 31 * result + (track ?: 0)
+ result = 31 * result + duration.hashCode()
+ return result
+ }
/** The URI for this song. */
- val uri: Uri get() = ContentUris.withAppendedId(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, internalMediaStoreId
- )
+ val uri: Uri
+ get() =
+ ContentUris.withAppendedId(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, internalMediaStoreId)
/** The duration of this song, in seconds (rounded down) */
- val seconds: Long get() = duration / 1000
+ val seconds: Long
+ get() = duration / 1000
/** The seconds of this song, but as a duration. */
- val formattedDuration: String get() = seconds.toDuration(false)
+ val formattedDuration: String
+ get() = seconds.toDuration(false)
private var mAlbum: Album? = null
/** The album of this song. */
- val album: Album get() = requireNotNull(mAlbum)
+ val album: Album
+ get() = requireNotNull(mAlbum)
private var mGenre: Genre? = null
/** The genre of this song. Will be an "unknown genre" if the song does not have any. */
- val genre: Genre get() = requireNotNull(mGenre)
+ val genre: Genre
+ get() = requireNotNull(mGenre)
/** An album name resolved to this song in particular. */
- val resolvedAlbumName: String get() =
- album.resolvedName
+ val resolvedAlbumName: String
+ get() = album.resolvedName
/** An artist name resolved to this song in particular. */
- val resolvedArtistName: String get() =
- internalMediaStoreArtistName ?: album.artist.resolvedName
+ val resolvedArtistName: String
+ get() = internalMediaStoreArtistName ?: album.artist.resolvedName
/** Internal field. Do not use. */
- val internalAlbumGroupingId: Long get() {
- var result = internalGroupingArtistName.lowercase().hashCode().toLong()
- result = 31 * result + internalMediaStoreAlbumName.lowercase().hashCode()
- return result
- }
+ val internalAlbumGroupingId: Long
+ get() {
+ var result = internalGroupingArtistName.lowercase().hashCode().toLong()
+ result = 31 * result + internalMediaStoreAlbumName.lowercase().hashCode()
+ return result
+ }
/** Internal field. Do not use. */
- val internalGroupingArtistName: String get() = internalMediaStoreAlbumArtistName
- ?: internalMediaStoreArtistName ?: MediaStore.UNKNOWN_STRING
+ val internalGroupingArtistName: String
+ get() =
+ internalMediaStoreAlbumArtistName
+ ?: internalMediaStoreArtistName ?: MediaStore.UNKNOWN_STRING
/** Internal field. Do not use. */
- val internalIsMissingAlbum: Boolean get() = mAlbum == null
+ val internalIsMissingAlbum: Boolean
+ get() = mAlbum == null
/** Internal field. Do not use. */
- val internalIsMissingArtist: Boolean get() = mAlbum?.internalIsMissingArtist ?: true
- /** Internal field. Do not use. **/
- val internalIsMissingGenre: Boolean get() = mGenre == null
+ val internalIsMissingArtist: Boolean
+ get() = mAlbum?.internalIsMissingArtist ?: true
+ /** Internal field. Do not use. */
+ val internalIsMissingGenre: Boolean
+ get() = mGenre == null
/** Internal method. Do not use. */
fun internalLinkAlbum(album: Album) {
@@ -145,9 +151,7 @@ data class Song(
}
}
-/**
- * The data object for an album.
- */
+/** The data object for an album. */
data class Album(
override val name: String,
/** The latest year of the songs in this album. Null if none of the songs had metadata. */
@@ -165,34 +169,37 @@ data class Album(
}
}
- override val id: Long get() {
- var result = name.hashCode().toLong()
- result = 31 * result + artist.name.hashCode()
- result = 31 * result + (year ?: 0)
- return result
- }
+ override val id: Long
+ get() {
+ var result = name.hashCode().toLong()
+ result = 31 * result + artist.name.hashCode()
+ result = 31 * result + (year ?: 0)
+ return result
+ }
override val resolvedName: String
get() = name
/** The formatted total duration of this album */
- val totalDuration: String get() =
- songs.sumOf { it.seconds }.toDuration(false)
+ val totalDuration: String
+ get() = songs.sumOf { it.seconds }.toDuration(false)
private var mArtist: Artist? = null
/** The parent artist of this album. */
- val artist: Artist get() = requireNotNull(mArtist)
+ val artist: Artist
+ get() = requireNotNull(mArtist)
/** The artist name, resolved to this album in particular. */
- val resolvedArtistName: String get() =
- artist.resolvedName
+ val resolvedArtistName: String
+ get() = artist.resolvedName
/** Internal field. Do not use. */
- val internalArtistGroupingId: Long get() =
- internalGroupingArtistName.lowercase().hashCode().toLong()
+ val internalArtistGroupingId: Long
+ get() = internalGroupingArtistName.lowercase().hashCode().toLong()
/** Internal field. Do not use. */
- val internalIsMissingArtist: Boolean get() = mArtist == null
+ val internalIsMissingArtist: Boolean
+ get() = mArtist == null
/** Internal method. Do not use. */
fun internalLinkArtist(artist: Artist) {
@@ -201,8 +208,8 @@ data class Album(
}
/**
- * The [MusicParent] for an *album* artist. This reflects a group of songs with the same(ish)
- * album artist or artist field, not the individual performers of an artist.
+ * The [MusicParent] for an *album* artist. This reflects a group of songs with the same(ish) album
+ * artist or artist field, not the individual performers of an artist.
*/
data class Artist(
override val name: String,
@@ -222,9 +229,7 @@ data class Artist(
val songs = albums.flatMap { it.songs }
}
-/**
- * The data object for a genre.
- */
+/** The data object for a genre. */
data class Genre(
override val name: String,
override val resolvedName: String,
@@ -239,13 +244,11 @@ data class Genre(
override val id = name.hashCode().toLong()
/** The formatted total duration of this genre */
- val totalDuration: String get() =
- songs.sumOf { it.seconds }.toDuration(false)
+ val totalDuration: String
+ get() = songs.sumOf { it.seconds }.toDuration(false)
}
-/**
- * A data object used solely for the "Header" UI element.
- */
+/** A data object used solely for the "Header" UI element. */
data class Header(
override val id: Long,
/** The string resource used for the header. */
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt
index 5f4debfa2..a7eb4da02 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.music
import android.content.ContentResolver
@@ -15,53 +32,52 @@ import org.oxycblt.auxio.util.logD
/**
* This class acts as the base for most the black magic required to get a remotely sensible music
* indexing system while still optimizing for time. I would recommend you leave this module now
- * before you lose your sanity trying to understand the hoops I had to jump through for this
- * system, but if you really want to stay, here's a debrief on why this code is so awful.
+ * before you lose your sanity trying to understand the hoops I had to jump through for this system,
+ * but if you really want to stay, here's a debrief on why this code is so awful.
*
* MediaStore is not a good API. It is not even a bad API. Calling it a bad API is an insult to
- * other bad android APIs, like CoordinatorLayout or InputMethodManager. No. MediaStore is a
- * crime against humanity and probably a way to summon Zalgo if you look at it the wrong way.
+ * other bad android APIs, like CoordinatorLayout or InputMethodManager. No. MediaStore is a crime
+ * against humanity and probably a way to summon Zalgo if you look at it the wrong way.
*
- * You think that if you wanted to query a song's genre from a media database, you could just
- * put "genre" in the query and it would return it, right? But not with MediaStore! No, that's
- * too straightforward for this contract that was dropped on it's head as a baby. So instead, you
- * have to query for each genre, query all the songs in each genre, and then iterate through those
- * songs to link every song with their genre. This is not documented anywhere, and the
- * O(mom im scared) algorithm you have to run to get it working single-handedly DOUBLES Auxio's
- * loading times. At no point have the devs considered that this system is absolutely insane, and
- * instead focused on adding infuriat- I mean nice proprietary extensions to MediaStore for their
- * own Google Play Music, and of course every Google Play Music user knew how great that turned
- * out!
+ * You think that if you wanted to query a song's genre from a media database, you could just put
+ * "genre" in the query and it would return it, right? But not with MediaStore! No, that's too
+ * straightforward for this contract that was dropped on it's head as a baby. So instead, you have
+ * to query for each genre, query all the songs in each genre, and then iterate through those songs
+ * to link every song with their genre. This is not documented anywhere, and the O(mom im scared)
+ * algorithm you have to run to get it working single-handedly DOUBLES Auxio's loading times. At no
+ * point have the devs considered that this system is absolutely insane, and instead focused on
+ * adding infuriat- I mean nice proprietary extensions to MediaStore for their own Google Play
+ * Music, and of course every Google Play Music user knew how great that turned out!
*
* It's not even ergonomics that makes this API bad. It's base implementation is completely borked
- * as well. Did you know that MediaStore doesn't accept dates that aren't from ID3v2.3 MP3 files?
- * I sure didn't, until I decided to upgrade my music collection to ID3v2.4 and FLAC only to see
- * that the metadata parser has a brain aneurysm the moment it stumbles upon a dreaded TRDC or
- * DATE tag. Once again, this is because internally android uses an ancient in-house metadata
- * parser to get everything indexed, and so far they have not bothered to modernize this parser
- * or even switch it to something more powerful like Taglib, not even in Android 12. ID3v2.4 has
- * been around for *21 years.* *It can drink now.* All of my what.
+ * as well. Did you know that MediaStore doesn't accept dates that aren't from ID3v2.3 MP3 files? I
+ * sure didn't, until I decided to upgrade my music collection to ID3v2.4 and FLAC only to see that
+ * the metadata parser has a brain aneurysm the moment it stumbles upon a dreaded TRDC or DATE tag.
+ * Once again, this is because internally android uses an ancient in-house metadata parser to get
+ * everything indexed, and so far they have not bothered to modernize this parser or even switch it
+ * to something more powerful like Taglib, not even in Android 12. ID3v2.4 has been around for *21
+ * years.* *It can drink now.* All of my what.
*
* Not to mention all the other infuriating quirks. Album artists can't be accessed from the albums
- * table, so we have to go for the less efficient "make a big query on all the songs lol" method
- * so that songs don't end up fragmented across artists. Pretty much every OEM has added some
- * extension or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH)
- * crippling the normal tables so that you're railroaded into their music app. The way I do
- * blacklisting relies on a semi-deprecated method, and the supposedly "modern" method is SLOWER and
- * causes even more problems since I have to manage databases across version boundaries. Sometimes
- * music will have a deformed clone that I can't filter out, sometimes Genres will just break for
- * no reason, and sometimes tags encoded in UTF-8 will be interpreted as anything from UTF-16 to
- * Latin-1 to *Shift JIS* WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY
+ * table, so we have to go for the less efficient "make a big query on all the songs lol" method so
+ * that songs don't end up fragmented across artists. Pretty much every OEM has added some extension
+ * or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH) crippling the
+ * normal tables so that you're railroaded into their music app. The way I do blacklisting relies on
+ * a semi-deprecated method, and the supposedly "modern" method is SLOWER and causes even more
+ * problems since I have to manage databases across version boundaries. Sometimes music will have a
+ * deformed clone that I can't filter out, sometimes Genres will just break for no reason, and
+ * sometimes tags encoded in UTF-8 will be interpreted as anything from UTF-16 to Latin-1 to *Shift
+ * JIS* WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY WHY
*
- * Is there anything we can do about it? No. Google has routinely shut down issues that begged google
- * to fix glaring issues with MediaStore or to just take the API behind the woodshed and shoot it.
- * Largely because they have zero incentive to improve it given how "obscure" local music listening
- * is. As a result, some players like Vanilla and VLC just hack their own pseudo-MediaStore
- * implementation from their own (better) parsers, but this is both infeasible for Auxio due to how
- * incredibly slow it is to get a file handle from the android sandbox AND how much harder it is to
- * manage a database of your own media that mirrors the filesystem perfectly. And even if I set
- * aside those crippling issues and changed my indexer to that, it would face the even larger
- * problem of how google keeps trying to kill the filesystem and force you into their
+ * Is there anything we can do about it? No. Google has routinely shut down issues that begged
+ * google to fix glaring issues with MediaStore or to just take the API behind the woodshed and
+ * shoot it. Largely because they have zero incentive to improve it given how "obscure" local music
+ * listening is. As a result, some players like Vanilla and VLC just hack their own
+ * pseudo-MediaStore implementation from their own (better) parsers, but this is both infeasible for
+ * Auxio due to how incredibly slow it is to get a file handle from the android sandbox AND how much
+ * harder it is to manage a database of your own media that mirrors the filesystem perfectly. And
+ * even if I set aside those crippling issues and changed my indexer to that, it would face the even
+ * larger problem of how google keeps trying to kill the filesystem and force you into their
* ContentResolver API. In the future MediaStore could be the only system we have, which is also the
* day that greenland melts and birthdays stop happening forever.
*
@@ -94,38 +110,30 @@ class MusicLoader {
for (song in songs) {
if (song.internalIsMissingAlbum ||
song.internalIsMissingArtist ||
- song.internalIsMissingGenre
- ) {
+ song.internalIsMissingGenre) {
throw IllegalStateException(
"Found malformed song: ${song.name} [" +
"album: ${!song.internalIsMissingAlbum} " +
"artist: ${!song.internalIsMissingArtist} " +
- "genre: ${!song.internalIsMissingGenre}]"
- )
+ "genre: ${!song.internalIsMissingGenre}]")
}
}
- return Library(
- genres,
- artists,
- albums,
- songs
- )
+ return Library(genres, artists, albums, songs)
}
/**
- * Gets a content resolver in a way that does not mangle metadata on
- * certain OEM skins. See https://github.com/OxygenCobalt/Auxio/issues/50
- * for more info.
+ * Gets a content resolver in a way that does not mangle metadata on certain OEM skins. See
+ * https://github.com/OxygenCobalt/Auxio/issues/50 for more info.
*/
- private val Context.contentResolverSafe: ContentResolver get() =
- applicationContext.contentResolver
+ private val Context.contentResolverSafe: ContentResolver
+ get() = applicationContext.contentResolver
/**
- * Does the initial query over the song database, including excluded directory
- * checks. The songs returned by this function are **not** well-formed. The
- * companion [buildAlbums], [buildArtists], and [readGenres] functions must be
- * called with the returned list so that all songs are properly linked up.
+ * Does the initial query over the song database, including excluded directory checks. The songs
+ * returned by this function are **not** well-formed. The companion [buildAlbums],
+ * [buildArtists], and [readGenres] functions must be called with the returned list so that all
+ * songs are properly linked up.
*/
private fun loadSongs(context: Context): List {
val blacklistDatabase = ExcludedDatabase.getInstance(context)
@@ -146,89 +154,100 @@ class MusicLoader {
var songs = mutableListOf()
context.contentResolverSafe.query(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- arrayOf(
- MediaStore.Audio.AudioColumns._ID,
- MediaStore.Audio.AudioColumns.TITLE,
- MediaStore.Audio.AudioColumns.DISPLAY_NAME,
- MediaStore.Audio.AudioColumns.TRACK,
- MediaStore.Audio.AudioColumns.DURATION,
- MediaStore.Audio.AudioColumns.YEAR,
- MediaStore.Audio.AudioColumns.ALBUM,
- MediaStore.Audio.AudioColumns.ALBUM_ID,
- MediaStore.Audio.AudioColumns.ARTIST,
- AUDIO_COLUMN_ALBUM_ARTIST
- ),
- selector, args.toTypedArray(), null
- )?.use { cursor ->
- val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
- val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE)
- val fileIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
- val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK)
- val durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION)
- val yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR)
- val albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM)
- val albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID)
- val artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST)
- val albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST)
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ arrayOf(
+ MediaStore.Audio.AudioColumns._ID,
+ MediaStore.Audio.AudioColumns.TITLE,
+ MediaStore.Audio.AudioColumns.DISPLAY_NAME,
+ MediaStore.Audio.AudioColumns.TRACK,
+ MediaStore.Audio.AudioColumns.DURATION,
+ MediaStore.Audio.AudioColumns.YEAR,
+ MediaStore.Audio.AudioColumns.ALBUM,
+ MediaStore.Audio.AudioColumns.ALBUM_ID,
+ MediaStore.Audio.AudioColumns.ARTIST,
+ AUDIO_COLUMN_ALBUM_ARTIST),
+ selector,
+ args.toTypedArray(),
+ null)
+ ?.use { cursor ->
+ val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
+ val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE)
+ val fileIndex =
+ cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
+ val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK)
+ val durationIndex =
+ cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION)
+ val yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR)
+ val albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM)
+ val albumIdIndex =
+ cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID)
+ val artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST)
+ val albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST)
- while (cursor.moveToNext()) {
- val id = cursor.getLong(idIndex)
- val title = cursor.getString(titleIndex)
- val fileName = cursor.getString(fileIndex)
+ while (cursor.moveToNext()) {
+ val id = cursor.getLong(idIndex)
+ val title = cursor.getString(titleIndex)
+ val fileName = cursor.getString(fileIndex)
- // The TRACK field is for some reason formatted as DTTT, where D is the disk
- // and T is the track. This is dumb and insane and forces me to mangle track
- // numbers above 1000, but there is nothing we can do that won't break the app
- // below API 30.
- // TODO: Disk number support?
- val track = cursor.getIntOrNull(trackIndex)?.mod(1000)
+ // The TRACK field is for some reason formatted as DTTT, where D is the disk
+ // and T is the track. This is dumb and insane and forces me to mangle track
+ // numbers above 1000, but there is nothing we can do that won't break the app
+ // below API 30.
+ // TODO: Disk number support?
+ val track = cursor.getIntOrNull(trackIndex)?.mod(1000)
- val duration = cursor.getLong(durationIndex)
- val year = cursor.getIntOrNull(yearIndex)
+ val duration = cursor.getLong(durationIndex)
+ val year = cursor.getIntOrNull(yearIndex)
- val album = cursor.getString(albumIndex)
- val albumId = cursor.getLong(albumIdIndex)
+ val album = cursor.getString(albumIndex)
+ val albumId = cursor.getLong(albumIdIndex)
- // If the artist field is , make it null. This makes handling the
- // insanity of the artist field easier later on.
- val artist = cursor.getStringOrNull(artistIndex)?.run {
- if (this == MediaStore.UNKNOWN_STRING) {
- null
- } else {
- this
- }
+ // If the artist field is , make it null. This makes handling the
+ // insanity of the artist field easier later on.
+ val artist =
+ cursor.getStringOrNull(artistIndex)?.run {
+ if (this == MediaStore.UNKNOWN_STRING) {
+ null
+ } else {
+ this
+ }
+ }
+
+ val albumArtist = cursor.getStringOrNull(albumArtistIndex)
+
+ // Note: Directory parsing is currently disabled until artist images are added.
+ // val dirs = cursor.getStringOrNull(dataIndex)?.run {
+ // substringBeforeLast("/", "").ifEmpty { null }
+ // }
+
+ songs.add(
+ Song(
+ title,
+ fileName,
+ duration,
+ track,
+ id,
+ year,
+ album,
+ albumId,
+ artist,
+ albumArtist,
+ ))
}
-
- val albumArtist = cursor.getStringOrNull(albumArtistIndex)
-
- // Note: Directory parsing is currently disabled until artist images are added.
- // val dirs = cursor.getStringOrNull(dataIndex)?.run {
- // substringBeforeLast("/", "").ifEmpty { null }
- // }
-
- songs.add(
- Song(
- title,
- fileName,
- duration,
- track,
- id,
- year,
- album,
- albumId,
- artist,
- albumArtist,
- )
- )
}
- }
// Deduplicate songs to prevent (most) deformed music clones
- songs = songs.distinctBy {
- it.name to it.internalMediaStoreAlbumName to it.internalMediaStoreArtistName to
- it.internalMediaStoreAlbumArtistName to it.track to it.duration
- }.toMutableList()
+ songs =
+ songs
+ .distinctBy {
+ it.name to
+ it.internalMediaStoreAlbumName to
+ it.internalMediaStoreArtistName to
+ it.internalMediaStoreAlbumArtistName to
+ it.track to
+ it.duration
+ }
+ .toMutableList()
logD("Successfully loaded ${songs.size} songs")
@@ -236,17 +255,17 @@ class MusicLoader {
}
/**
- * Group songs up into their respective albums. Instead of using the unreliable album or
- * artist databases, we instead group up songs by their *lowercase* artist and album name
- * to create albums. This serves two purposes:
- * 1. Sometimes artist names can be styled differently, e.g "Rammstein" vs. "RAMMSTEIN".
- * This makes sure both of those are resolved into a single artist called "Rammstein"
- * 2. Sometimes MediaStore will split album IDs up if the songs differ in format. This
- * ensures that all songs are unified under a single album.
+ * Group songs up into their respective albums. Instead of using the unreliable album or artist
+ * databases, we instead group up songs by their *lowercase* artist and album name to create
+ * albums. This serves two purposes:
+ * 1. Sometimes artist names can be styled differently, e.g "Rammstein" vs. "RAMMSTEIN". This
+ * makes sure both of those are resolved into a single artist called "Rammstein"
+ * 2. Sometimes MediaStore will split album IDs up if the songs differ in format. This ensures
+ * that all songs are unified under a single album.
*
- * This does come with some costs, it's far slower than using the album ID itself, and
- * it may result in an unrelated album art being selected depending on the song chosen
- * as the template, but it seems to work pretty well.
+ * This does come with some costs, it's far slower than using the album ID itself, and it may
+ * result in an unrelated album art being selected depending on the song chosen as the template,
+ * but it seems to work pretty well.
*/
private fun buildAlbums(songs: List): List {
val albums = mutableListOf()
@@ -259,15 +278,14 @@ class MusicLoader {
// This allows us to replicate the LAST_YEAR field, which is useful as it means that
// weird years like "0" wont show up if there are alternatives.
// TODO: Weigh songs with null years lower than songs with zero years
- val templateSong = requireNotNull(
- albumSongs.maxByOrNull { it.internalMediaStoreYear ?: 0 }
- )
+ val templateSong =
+ requireNotNull(albumSongs.maxByOrNull { it.internalMediaStoreYear ?: 0 })
val albumName = templateSong.internalMediaStoreAlbumName
val albumYear = templateSong.internalMediaStoreYear
- val albumCoverUri = ContentUris.withAppendedId(
- Uri.parse("content://media/external/audio/albumart"),
- templateSong.internalMediaStoreAlbumId
- )
+ val albumCoverUri =
+ ContentUris.withAppendedId(
+ Uri.parse("content://media/external/audio/albumart"),
+ templateSong.internalMediaStoreAlbumId)
val artistName = templateSong.internalGroupingArtistName
albums.add(
@@ -277,8 +295,7 @@ class MusicLoader {
albumCoverUri,
albumSongs,
artistName,
- )
- )
+ ))
}
logD("Successfully built ${albums.size} albums")
@@ -287,8 +304,8 @@ class MusicLoader {
}
/**
- * Group up albums into artists. This also requires a de-duplication step due to some
- * edge cases where [buildAlbums] could not detect duplicates.
+ * Group up albums into artists. This also requires a de-duplication step due to some edge cases
+ * where [buildAlbums] could not detect duplicates.
*/
private fun buildArtists(context: Context, albums: List): List {
val artists = mutableListOf()
@@ -297,19 +314,14 @@ class MusicLoader {
for (entry in albumsByArtist) {
val templateAlbum = entry.value[0]
val artistName = templateAlbum.internalGroupingArtistName
- val resolvedName = when (templateAlbum.internalGroupingArtistName) {
- MediaStore.UNKNOWN_STRING -> context.getString(R.string.def_artist)
- else -> artistName
- }
+ val resolvedName =
+ when (templateAlbum.internalGroupingArtistName) {
+ MediaStore.UNKNOWN_STRING -> context.getString(R.string.def_artist)
+ else -> artistName
+ }
val artistAlbums = entry.value
- artists.add(
- Artist(
- artistName,
- resolvedName,
- artistAlbums
- )
- )
+ artists.add(Artist(artistName, resolvedName, artistAlbums))
}
logD("Successfully built ${artists.size} artists")
@@ -318,50 +330,45 @@ class MusicLoader {
}
/**
- * Read all genres and link them up to the given songs. This is the code that
- * requires me to make dozens of useless queries just to link genres up.
+ * Read all genres and link them up to the given songs. This is the code that requires me to
+ * make dozens of useless queries just to link genres up.
*/
private fun readGenres(context: Context, songs: List): List {
val genres = mutableListOf()
context.contentResolverSafe.query(
- MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
- arrayOf(
- MediaStore.Audio.Genres._ID,
- MediaStore.Audio.Genres.NAME
- ),
- null, null, null
- )?.use { cursor ->
- val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID)
- val nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME)
+ MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
+ arrayOf(MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres.NAME),
+ null,
+ null,
+ null)
+ ?.use { cursor ->
+ val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID)
+ val nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME)
- while (cursor.moveToNext()) {
- // Genre names can be a normal name, an ID3v2 constant, or null. Normal names are
- // resolved as usual, but null values don't make sense and are often junk anyway,
- // so we skip genres that have them.
- val id = cursor.getLong(idIndex)
- val name = cursor.getStringOrNull(nameIndex) ?: continue
- val resolvedName = name.genreNameCompat ?: name
- val genreSongs = queryGenreSongs(context, id, songs) ?: continue
+ while (cursor.moveToNext()) {
+ // Genre names can be a normal name, an ID3v2 constant, or null. Normal names
+ // are
+ // resolved as usual, but null values don't make sense and are often junk
+ // anyway,
+ // so we skip genres that have them.
+ val id = cursor.getLong(idIndex)
+ val name = cursor.getStringOrNull(nameIndex) ?: continue
+ val resolvedName = name.genreNameCompat ?: name
+ val genreSongs = queryGenreSongs(context, id, songs) ?: continue
- genres.add(
- Genre(
- name,
- resolvedName,
- genreSongs
- )
- )
+ genres.add(Genre(name, resolvedName, genreSongs))
+ }
}
- }
val songsWithoutGenres = songs.filter { it.internalIsMissingGenre }
if (songsWithoutGenres.isNotEmpty()) {
// Songs that don't have a genre will be thrown into an unknown genre.
- val unknownGenre = Genre(
- name = MediaStore.UNKNOWN_STRING,
- resolvedName = context.getString(R.string.def_genre),
- songsWithoutGenres
- )
+ val unknownGenre =
+ Genre(
+ name = MediaStore.UNKNOWN_STRING,
+ resolvedName = context.getString(R.string.def_genre),
+ songsWithoutGenres)
genres.add(unknownGenre)
}
@@ -372,60 +379,63 @@ class MusicLoader {
}
/**
- * Decodes the genre name from an ID3(v2) constant. See [genreConstantTable] for the
- * genre constant map that Auxio uses.
+ * Decodes the genre name from an ID3(v2) constant. See [genreConstantTable] for the genre
+ * constant map that Auxio uses.
*/
- private val String.genreNameCompat: String? get() {
- if (isDigitsOnly()) {
- // ID3v1, just parse as an integer
- return genreConstantTable.getOrNull(toInt())
- }
-
- if (startsWith('(') && endsWith(')')) {
- // ID3v2.3/ID3v2.4, parse out the parentheses and get the integer
- // Any genres formatted as "(CHARS)" will be ignored.
- val genreInt = substring(1 until lastIndex).toIntOrNull()
- if (genreInt != null) {
- return genreConstantTable.getOrNull(genreInt)
+ private val String.genreNameCompat: String?
+ get() {
+ if (isDigitsOnly()) {
+ // ID3v1, just parse as an integer
+ return genreConstantTable.getOrNull(toInt())
}
- }
- // Current name is fine.
- return null
- }
+ if (startsWith('(') && endsWith(')')) {
+ // ID3v2.3/ID3v2.4, parse out the parentheses and get the integer
+ // Any genres formatted as "(CHARS)" will be ignored.
+ val genreInt = substring(1 until lastIndex).toIntOrNull()
+ if (genreInt != null) {
+ return genreConstantTable.getOrNull(genreInt)
+ }
+ }
+
+ // Current name is fine.
+ return null
+ }
/**
- * Queries the genre songs for [genreId]. Some genres are insane and don't contain songs
- * for some reason, so if that's the case then this function will return null.
+ * Queries the genre songs for [genreId]. Some genres are insane and don't contain songs for
+ * some reason, so if that's the case then this function will return null.
*/
private fun queryGenreSongs(context: Context, genreId: Long, songs: List): List? {
val genreSongs = mutableListOf()
// Don't even bother blacklisting here as useless iterations are less expensive than IO
context.contentResolverSafe.query(
- MediaStore.Audio.Genres.Members.getContentUri("external", genreId),
- arrayOf(MediaStore.Audio.Genres.Members._ID),
- null, null, null
- )?.use { cursor ->
- val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID)
+ MediaStore.Audio.Genres.Members.getContentUri("external", genreId),
+ arrayOf(MediaStore.Audio.Genres.Members._ID),
+ null,
+ null,
+ null)
+ ?.use { cursor ->
+ val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID)
- while (cursor.moveToNext()) {
- val id = cursor.getLong(idIndex)
- songs.find { it.internalMediaStoreId == id }?.let { song ->
- genreSongs.add(song)
+ while (cursor.moveToNext()) {
+ val id = cursor.getLong(idIndex)
+ songs.find { it.internalMediaStoreId == id }?.let { song ->
+ genreSongs.add(song)
+ }
}
}
- }
return genreSongs.ifEmpty { null }
}
companion object {
/**
- * The album_artist MediaStore field has existed since at least API 21, but until API
- * 30 it was a proprietary extension for Google Play Music and was not documented.
- * Since this field probably works on all versions Auxio supports, we suppress the
- * warning about using a possibly-unsupported constant.
+ * The album_artist MediaStore field has existed since at least API 21, but until API 30 it
+ * was a proprietary extension for Google Play Music and was not documented. Since this
+ * field probably works on all versions Auxio supports, we suppress the warning about using
+ * a possibly-unsupported constant.
*/
@Suppress("InlinedApi")
private const val AUDIO_COLUMN_ALBUM_ARTIST = MediaStore.Audio.AudioColumns.ALBUM_ARTIST
@@ -434,42 +444,205 @@ class MusicLoader {
* A complete table of all the constant genre values for ID3(v2), including non-standard
* extensions.
*/
- private val genreConstantTable = arrayOf(
- // ID3 Standard
- "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop",
- "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock",
- "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack",
- "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance",
- "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise",
- "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop",
- "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic",
- "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
- "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret",
- "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal",
- "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock",
+ private val genreConstantTable =
+ arrayOf(
+ // ID3 Standard
+ "Blues",
+ "Classic Rock",
+ "Country",
+ "Dance",
+ "Disco",
+ "Funk",
+ "Grunge",
+ "Hip-Hop",
+ "Jazz",
+ "Metal",
+ "New Age",
+ "Oldies",
+ "Other",
+ "Pop",
+ "R&B",
+ "Rap",
+ "Reggae",
+ "Rock",
+ "Techno",
+ "Industrial",
+ "Alternative",
+ "Ska",
+ "Death Metal",
+ "Pranks",
+ "Soundtrack",
+ "Euro-Techno",
+ "Ambient",
+ "Trip-Hop",
+ "Vocal",
+ "Jazz+Funk",
+ "Fusion",
+ "Trance",
+ "Classical",
+ "Instrumental",
+ "Acid",
+ "House",
+ "Game",
+ "Sound Clip",
+ "Gospel",
+ "Noise",
+ "AlternRock",
+ "Bass",
+ "Soul",
+ "Punk",
+ "Space",
+ "Meditative",
+ "Instrumental Pop",
+ "Instrumental Rock",
+ "Ethnic",
+ "Gothic",
+ "Darkwave",
+ "Techno-Industrial",
+ "Electronic",
+ "Pop-Folk",
+ "Eurodance",
+ "Dream",
+ "Southern Rock",
+ "Comedy",
+ "Cult",
+ "Gangsta",
+ "Top 40",
+ "Christian Rap",
+ "Pop/Funk",
+ "Jungle",
+ "Native American",
+ "Cabaret",
+ "New Wave",
+ "Psychadelic",
+ "Rave",
+ "Showtunes",
+ "Trailer",
+ "Lo-Fi",
+ "Tribal",
+ "Acid Punk",
+ "Acid Jazz",
+ "Polka",
+ "Retro",
+ "Musical",
+ "Rock & Roll",
+ "Hard Rock",
- // Winamp extensions, more or less a de-facto standard
- "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin",
- "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock",
- "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus",
- "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera", "Chamber Music",
- "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam",
- "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul",
- "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",
- "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "Britpop",
- "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal",
- "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa",
- "Thrash Metal", "Anime", "JPop", "Synthpop",
+ // Winamp extensions, more or less a de-facto standard
+ "Folk",
+ "Folk-Rock",
+ "National Folk",
+ "Swing",
+ "Fast Fusion",
+ "Bebob",
+ "Latin",
+ "Revival",
+ "Celtic",
+ "Bluegrass",
+ "Avantgarde",
+ "Gothic Rock",
+ "Progressive Rock",
+ "Psychedelic Rock",
+ "Symphonic Rock",
+ "Slow Rock",
+ "Big Band",
+ "Chorus",
+ "Easy Listening",
+ "Acoustic",
+ "Humour",
+ "Speech",
+ "Chanson",
+ "Opera",
+ "Chamber Music",
+ "Sonata",
+ "Symphony",
+ "Booty Bass",
+ "Primus",
+ "Porn Groove",
+ "Satire",
+ "Slow Jam",
+ "Club",
+ "Tango",
+ "Samba",
+ "Folklore",
+ "Ballad",
+ "Power Ballad",
+ "Rhythmic Soul",
+ "Freestyle",
+ "Duet",
+ "Punk Rock",
+ "Drum Solo",
+ "A capella",
+ "Euro-House",
+ "Dance Hall",
+ "Goa",
+ "Drum & Bass",
+ "Club-House",
+ "Hardcore",
+ "Terror",
+ "Indie",
+ "Britpop",
+ "Negerpunk",
+ "Polsk Punk",
+ "Beat",
+ "Christian Gangsta",
+ "Heavy Metal",
+ "Black Metal",
+ "Crossover",
+ "Contemporary Christian",
+ "Christian Rock",
+ "Merengue",
+ "Salsa",
+ "Thrash Metal",
+ "Anime",
+ "JPop",
+ "Synthpop",
- // Winamp 5.6+ extensions, also used by EasyTAG.
- // I only include this because post-rock is a based genre and deserves a slot.
- "Abstract", "Art Rock", "Baroque", "Bhangra", "Big Beat", "Breakbeat", "Chillout",
- "Downtempo", "Dub", "EBM", "Eclectic", "Electro", "Electroclash", "Emo", "Experimental",
- "Garage", "Global", "IDM", "Illbient", "Industro-Goth", "Jam Band", "Krautrock",
- "Leftfield", "Lounge", "Math Rock", "New Romantic", "Nu-Breakz", "Post-Punk",
- "Post-Rock", "Psytrance", "Shoegaze", "Space Rock", "Trop Rock", "World Music",
- "Neoclassical", "Audiobook", "Audio Theatre", "Neue Deutsche Welle", "Podcast",
- "Indie Rock", "G-Funk", "Dubstep", "Garage Rock", "Psybient"
- )
+ // Winamp 5.6+ extensions, also used by EasyTAG.
+ // I only include this because post-rock is a based genre and deserves a slot.
+ "Abstract",
+ "Art Rock",
+ "Baroque",
+ "Bhangra",
+ "Big Beat",
+ "Breakbeat",
+ "Chillout",
+ "Downtempo",
+ "Dub",
+ "EBM",
+ "Eclectic",
+ "Electro",
+ "Electroclash",
+ "Emo",
+ "Experimental",
+ "Garage",
+ "Global",
+ "IDM",
+ "Illbient",
+ "Industro-Goth",
+ "Jam Band",
+ "Krautrock",
+ "Leftfield",
+ "Lounge",
+ "Math Rock",
+ "New Romantic",
+ "Nu-Breakz",
+ "Post-Punk",
+ "Post-Rock",
+ "Psytrance",
+ "Shoegaze",
+ "Space Rock",
+ "Trop Rock",
+ "World Music",
+ "Neoclassical",
+ "Audiobook",
+ "Audio Theatre",
+ "Neue Deutsche Welle",
+ "Podcast",
+ "Indie Rock",
+ "G-Funk",
+ "Dubstep",
+ "Garage Rock",
+ "Psybient")
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt
index 95cf81e9a..5d927b4ce 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * MusicStore.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music
import android.Manifest
@@ -25,41 +24,42 @@ import android.content.pm.PackageManager
import android.net.Uri
import android.provider.OpenableColumns
import androidx.core.content.ContextCompat
+import java.lang.Exception
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
-import java.lang.Exception
/**
- * The main storage for music items.
- * Getting an instance of this object is more complicated as it loads asynchronously.
- * See the companion object for more.
- * TODO: Add automatic rescanning [major change]
+ * The main storage for music items. Getting an instance of this object is more complicated as it
+ * loads asynchronously. See the companion object for more. TODO: Add automatic rescanning [major
+ * change]
* @author OxygenCobalt
*/
class MusicStore private constructor() {
private var mGenres = listOf()
- val genres: List get() = mGenres
+ val genres: List
+ get() = mGenres
private var mArtists = listOf()
- val artists: List get() = mArtists
+ val artists: List
+ get() = mArtists
private var mAlbums = listOf()
- val albums: List get() = mAlbums
+ val albums: List
+ get() = mAlbums
private var mSongs = listOf()
- val songs: List get() = mSongs
+ val songs: List
+ get() = mSongs
- /**
- * Load/Sort the entire music library. Should always be ran on a coroutine.
- */
+ /** Load/Sort the entire music library. Should always be ran on a coroutine. */
private fun load(context: Context): Response {
logD("Starting initial music load")
- val notGranted = ContextCompat.checkSelfPermission(
- context, Manifest.permission.READ_EXTERNAL_STORAGE
- ) == PackageManager.PERMISSION_DENIED
+ val notGranted =
+ ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) ==
+ PackageManager.PERMISSION_DENIED
if (notGranted) {
return Response.Err(ErrorKind.NO_PERMS)
@@ -69,8 +69,7 @@ class MusicStore private constructor() {
val start = System.currentTimeMillis()
val loader = MusicLoader()
- val library = loader.load(context)
- ?: return Response.Err(ErrorKind.NO_MUSIC)
+ val library = loader.load(context) ?: return Response.Err(ErrorKind.NO_MUSIC)
mSongs = library.songs
mAlbums = library.albums
@@ -87,27 +86,22 @@ class MusicStore private constructor() {
return Response.Ok(this)
}
- /**
- * Find a song in a faster manner using an ID for its album as well.
- */
+ /** Find a song in a faster manner using an ID for its album as well. */
fun findSongFast(songId: Long, albumId: Long): Song? {
return albums.find { it.id == albumId }?.songs?.find { it.id == songId }
}
/**
- * Find a song for a [uri], this is similar to [findSongFast], but with some kind of content uri.
+ * Find a song for a [uri], this is similar to [findSongFast], but with some kind of content
+ * uri.
* @return The corresponding [Song] for this [uri], null if there isn't one.
*/
fun findSongForUri(uri: Uri, resolver: ContentResolver): Song? {
- resolver.query(
- uri,
- arrayOf(OpenableColumns.DISPLAY_NAME),
- null, null, null
- )?.use { cursor ->
+ resolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor
+ ->
cursor.moveToFirst()
- val fileName = cursor.getString(
- cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
- )
+ val fileName =
+ cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
return songs.find { it.fileName == fileName }
}
@@ -116,9 +110,8 @@ class MusicStore private constructor() {
}
/**
- * A response that [MusicStore] returns when loading music.
- * And before you ask, yes, I do like rust.
- * TODO: Add the exception to the "FAILED" ErrorKind
+ * A response that [MusicStore] returns when loading music. And before you ask, yes, I do like
+ * rust. TODO: Add the exception to the "FAILED" ErrorKind
*/
sealed class Response {
class Ok(val musicStore: MusicStore) : Response()
@@ -126,12 +119,13 @@ class MusicStore private constructor() {
}
enum class ErrorKind {
- NO_PERMS, NO_MUSIC, FAILED
+ NO_PERMS,
+ NO_MUSIC,
+ FAILED
}
companion object {
- @Volatile
- private var RESPONSE: Response? = null
+ @Volatile private var RESPONSE: Response? = null
/**
* Initialize the loading process for this instance. This must be ran on a background
@@ -145,41 +139,41 @@ class MusicStore private constructor() {
return currentInstance
}
- val response = withContext(Dispatchers.IO) {
- val response = MusicStore().load(context)
- synchronized(this) {
- RESPONSE = response
+ val response =
+ withContext(Dispatchers.IO) {
+ val response = MusicStore().load(context)
+ synchronized(this) { RESPONSE = response }
+ response
}
- response
- }
return response
}
/**
- * Await the successful creation of a [MusicStore] instance. The co-routine calling
- * this will block until the successful creation of a [MusicStore], in which it will
- * then be returned.
+ * Await the successful creation of a [MusicStore] instance. The co-routine calling this
+ * will block until the successful creation of a [MusicStore], in which it will then be
+ * returned.
*/
- suspend fun awaitInstance() = withContext(Dispatchers.Default) {
- // We have to do a withContext call so we don't block the JVM thread
- val musicStore: MusicStore
+ suspend fun awaitInstance() =
+ withContext(Dispatchers.Default) {
+ // We have to do a withContext call so we don't block the JVM thread
+ val musicStore: MusicStore
- while (true) {
- val response = RESPONSE
+ while (true) {
+ val response = RESPONSE
- if (response is Response.Ok) {
- musicStore = response.musicStore
- break
+ if (response is Response.Ok) {
+ musicStore = response.musicStore
+ break
+ }
}
+
+ musicStore
}
- musicStore
- }
-
/**
- * Maybe get a MusicStore instance. This is useful if you are running code while the
- * loading process may still be going on.
+ * Maybe get a MusicStore instance. This is useful if you are running code while the loading
+ * process may still be going on.
*
* @return null if the music store instance is still loading or if the loading process has
* encountered an error. An instance is returned otherwise.
@@ -195,9 +189,8 @@ class MusicStore private constructor() {
}
/**
- * Require a MusicStore instance. This function is dangerous and should only be used if
- * it's guaranteed that the caller's code will only be called after the initial loading
- * process.
+ * Require a MusicStore instance. This function is dangerous and should only be used if it's
+ * guaranteed that the caller's code will only be called after the initial loading process.
*/
fun requireInstance(): MusicStore {
return requireNotNull(maybeGetInstance()) {
@@ -205,9 +198,7 @@ class MusicStore private constructor() {
}
}
- /**
- * Check if this instance has successfully loaded or not.
- */
+ /** Check if this instance has successfully loaded or not. */
fun loaded(): Boolean {
return maybeGetInstance() != null
}
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt
index 5c03a5e95..ac7631505 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * MusicUtils.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music
import android.text.format.DateUtils
@@ -30,8 +29,8 @@ import org.oxycblt.auxio.util.logW
/**
* Convert a [Long] of seconds into a string duration.
- * @param isElapsed Whether this duration is represents elapsed time. If this is false, then
- * --:-- will be returned if the second value is 0.
+ * @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
+ * will be returned if the second value is 0.
*/
fun Long.toDuration(isElapsed: Boolean): String {
if (!isElapsed && this == 0L) {
@@ -58,11 +57,7 @@ fun TextView.bindSongInfo(song: Song?) {
return
}
- text = context.getString(
- R.string.fmt_two,
- song.resolvedArtistName,
- song.resolvedAlbumName
- )
+ text = context.getString(R.string.fmt_two, song.resolvedArtistName, song.resolvedAlbumName)
}
@BindingAdapter("albumInfo")
@@ -72,11 +67,11 @@ fun TextView.bindAlbumInfo(album: Album?) {
return
}
- text = context.getString(
- R.string.fmt_two,
- album.resolvedArtistName,
- context.getPluralSafe(R.plurals.fmt_song_count, album.songs.size)
- )
+ text =
+ context.getString(
+ R.string.fmt_two,
+ album.resolvedArtistName,
+ context.getPluralSafe(R.plurals.fmt_song_count, album.songs.size))
}
@BindingAdapter("artistInfo")
@@ -86,11 +81,11 @@ fun TextView.bindArtistInfo(artist: Artist?) {
return
}
- text = context.getString(
- R.string.fmt_two,
- context.getPluralSafe(R.plurals.fmt_album_count, artist.albums.size),
- context.getPluralSafe(R.plurals.fmt_song_count, artist.songs.size)
- )
+ text =
+ context.getString(
+ R.string.fmt_two,
+ context.getPluralSafe(R.plurals.fmt_album_count, artist.albums.size),
+ context.getPluralSafe(R.plurals.fmt_song_count, artist.songs.size))
}
@BindingAdapter("genreInfo")
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt
index b91adb12f..ae13ee2a2 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * MusicViewModel.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music
import android.content.Context
@@ -33,8 +32,8 @@ class MusicViewModel : ViewModel() {
private var isBusy = false
/**
- * Initiate the loading process. This is done here since HomeFragment will be the first
- * fragment navigated to and because SnackBars will have the best UX here.
+ * Initiate the loading process. This is done here since HomeFragment will be the first fragment
+ * navigated to and because SnackBars will have the best UX here.
*/
fun loadMusic(context: Context) {
if (mLoaderResponse.value != null || isBusy) {
diff --git a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDatabase.kt b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDatabase.kt
index 3267b986d..875ce90d5 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDatabase.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDatabase.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * BlacklistDatabase.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music.excluded
import android.content.ContentValues
@@ -28,9 +27,9 @@ import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.queryAll
/**
- * Database for storing excluded directories.
- * Note that the paths stored here will not work with MediaStore unless you append a "%" at the end.
- * Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
+ * Database for storing excluded directories. Note that the paths stored here will not work with
+ * MediaStore unless you append a "%" at the end. Yes. I know Room exists. But that would needlessly
+ * bloat my app and has crippling bugs.
* @author OxygenCobalt
*/
class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
@@ -47,9 +46,7 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
onUpgrade(db, newVersion, oldVersion)
}
- /**
- * Write a list of [paths] to the database.
- */
+ /** Write a list of [paths] to the database. */
fun writePaths(paths: List) {
assertBackgroundThread()
@@ -58,21 +55,14 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
logD("Deleted paths db")
for (path in paths) {
- insert(
- TABLE_NAME, null,
- ContentValues(1).apply {
- put(COLUMN_PATH, path)
- }
- )
+ insert(TABLE_NAME, null, ContentValues(1).apply { put(COLUMN_PATH, path) })
}
logD("Successfully wrote ${paths.size} paths to db")
}
}
- /**
- * Get the current list of paths from the database.
- */
+ /** Get the current list of paths from the database. */
fun readPaths(): List {
assertBackgroundThread()
@@ -97,12 +87,9 @@ class ExcludedDatabase(context: Context) : SQLiteOpenHelper(context, DB_NAME, nu
const val TABLE_NAME = "blacklist_dirs_table"
const val COLUMN_PATH = "COLUMN_PATH"
- @Volatile
- private var INSTANCE: ExcludedDatabase? = null
+ @Volatile private var INSTANCE: ExcludedDatabase? = null
- /**
- * Get/Instantiate the single instance of [ExcludedDatabase].
- */
+ /** Get/Instantiate the single instance of [ExcludedDatabase]. */
fun getInstance(context: Context): ExcludedDatabase {
val currentInstance = INSTANCE
diff --git a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDialog.kt
index b45aa4ed5..6296ba728 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDialog.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * BlacklistDialog.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music.excluded
import android.net.Uri
@@ -57,13 +56,10 @@ class ExcludedDialog : LifecycleDialog() {
): View {
val binding = DialogExcludedBinding.inflate(inflater)
- val adapter = ExcludedEntryAdapter { path ->
- excludedModel.removePath(path)
- }
+ val adapter = ExcludedEntryAdapter { path -> excludedModel.removePath(path) }
- val launcher = registerForActivityResult(
- ActivityResultContracts.OpenDocumentTree(), ::addDocTreePath
- )
+ val launcher =
+ registerForActivityResult(ActivityResultContracts.OpenDocumentTree(), ::addDocTreePath)
// --- UI SETUP ---
@@ -131,9 +127,9 @@ class ExcludedDialog : LifecycleDialog() {
private fun parseDocTreePath(uri: Uri): String? {
// Turn the raw URI into a document tree URI
- val docUri = DocumentsContract.buildDocumentUriUsingTree(
- uri, DocumentsContract.getTreeDocumentId(uri)
- )
+ val docUri =
+ DocumentsContract.buildDocumentUriUsingTree(
+ uri, DocumentsContract.getTreeDocumentId(uri))
// Turn it into a semi-usable path
val typeAndPath = DocumentsContract.getTreeDocumentId(docUri).split(":")
@@ -153,15 +149,11 @@ class ExcludedDialog : LifecycleDialog() {
private fun saveAndRestart() {
excludedModel.save {
- playbackModel.savePlaybackState(requireContext()) {
- requireContext().hardRestart()
- }
+ playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() }
}
}
- /**
- * Get *just* the root path, nothing else is really needed.
- */
+ /** Get *just* the root path, nothing else is really needed. */
private fun getRootPath(): String {
return Environment.getExternalStorageDirectory().absolutePath
}
diff --git a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedEntryAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedEntryAdapter.kt
index e5763439e..66ea7bfff 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedEntryAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedEntryAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * BlacklistEntryAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music.excluded
import android.annotation.SuppressLint
@@ -28,9 +27,8 @@ import org.oxycblt.auxio.util.inflater
* Adapter that shows the excluded directories and their "Clear" button.
* @author OxygenCobalt
*/
-class ExcludedEntryAdapter(
- private val onClear: (String) -> Unit
-) : RecyclerView.Adapter() {
+class ExcludedEntryAdapter(private val onClear: (String) -> Unit) :
+ RecyclerView.Adapter() {
private var paths = mutableListOf()
override fun getItemCount() = paths.size
@@ -49,21 +47,18 @@ class ExcludedEntryAdapter(
notifyDataSetChanged()
}
- inner class ViewHolder(
- private val binding: ItemExcludedDirBinding
- ) : RecyclerView.ViewHolder(binding.root) {
+ inner class ViewHolder(private val binding: ItemExcludedDirBinding) :
+ RecyclerView.ViewHolder(binding.root) {
init {
- binding.root.layoutParams = RecyclerView.LayoutParams(
- RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT
- )
+ binding.root.layoutParams =
+ RecyclerView.LayoutParams(
+ RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
}
fun bind(path: String) {
binding.excludedPath.text = path
binding.excludedPath.requestLayout()
- binding.excludedClear.setOnClickListener {
- onClear(path)
- }
+ binding.excludedClear.setOnClickListener { onClear(path) }
}
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedViewModel.kt
index 41dda89d8..48104a6fd 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedViewModel.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * BlacklistViewModel.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.music.excluded
import android.content.Context
@@ -30,29 +29,28 @@ import kotlinx.coroutines.withContext
import org.oxycblt.auxio.util.logD
/**
- * ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal
- * of paths. Use [Factory] to instantiate this.
- * TODO: Unify with MusicViewModel
+ * ViewModel that acts as a wrapper around [ExcludedDatabase], allowing for the addition/removal of
+ * paths. Use [Factory] to instantiate this. TODO: Unify with MusicViewModel
* @author OxygenCobalt
*/
class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewModel() {
private val mPaths = MutableLiveData(mutableListOf())
- val paths: LiveData> get() = mPaths
+ val paths: LiveData>
+ get() = mPaths
private var dbPaths = listOf()
- /**
- * Check if changes have been made to the ViewModel's paths.
- */
- val isModified: Boolean get() = dbPaths != paths.value
+ /** Check if changes have been made to the ViewModel's paths. */
+ val isModified: Boolean
+ get() = dbPaths != paths.value
init {
loadDatabasePaths()
}
/**
- * Add a path to this ViewModel. It will not write the path to the database unless
- * [save] is called.
+ * Add a path to this ViewModel. It will not write the path to the database unless [save] is
+ * called.
*/
fun addPath(path: String) {
if (!mPaths.value!!.contains(path)) {
@@ -70,9 +68,7 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
mPaths.value = mPaths.value
}
- /**
- * Save the pending paths to the database. [onDone] will be called on completion.
- */
+ /** Save the pending paths to the database. [onDone] will be called on completion. */
fun save(onDone: () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
val start = System.currentTimeMillis()
@@ -80,24 +76,18 @@ class ExcludedViewModel(private val excludedDatabase: ExcludedDatabase) : ViewMo
dbPaths = mPaths.value!!
onDone()
this@ExcludedViewModel.logD(
- "Path save completed successfully in ${System.currentTimeMillis() - start}ms"
- )
+ "Path save completed successfully in ${System.currentTimeMillis() - start}ms")
}
}
- /**
- * Load the paths stored in the database to this ViewModel, will erase any pending changes.
- */
+ /** Load the paths stored in the database to this ViewModel, will erase any pending changes. */
private fun loadDatabasePaths() {
viewModelScope.launch(Dispatchers.IO) {
val start = System.currentTimeMillis()
dbPaths = excludedDatabase.readPaths()
- withContext(Dispatchers.Main) {
- mPaths.value = dbPaths.toMutableList()
- }
+ withContext(Dispatchers.Main) { mPaths.value = dbPaths.toMutableList() }
this@ExcludedViewModel.logD(
- "Path load completed successfully in ${System.currentTimeMillis() - start}ms"
- )
+ "Path load completed successfully in ${System.currentTimeMillis() - start}ms")
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt
index caf863c05..10059b68c 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarView.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * CompactPlaybackView.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback
import android.content.Context
@@ -35,14 +34,13 @@ import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.systemBarInsetsCompat
/**
- * A view displaying the playback state in a compact manner. This is only meant to be used
- * by [PlaybackLayout].
+ * A view displaying the playback state in a compact manner. This is only meant to be used by
+ * [PlaybackLayout].
*/
-class PlaybackBarView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr) {
+class PlaybackBarView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ ConstraintLayout(context, attrs, defStyleAttr) {
private val binding = ViewPlaybackBarBinding.inflate(context.inflater, this, true)
init {
@@ -52,35 +50,29 @@ class PlaybackBarView @JvmOverloads constructor(
// we use colorSecondary instead of colorSurfaceVariant. This is because
// colorSurfaceVariant is used with the assumption that the view that is using it is
// not elevated and is therefore not colored. This view is elevated.
- binding.playbackProgressBar.trackColor = MaterialColors.compositeARGBWithAlpha(
- context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt()
- )
+ binding.playbackProgressBar.trackColor =
+ MaterialColors.compositeARGBWithAlpha(
+ context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt())
}
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
// Since we swipe up this view, we need to make sure it does not collide with
// any gesture events. So, apply the system gesture insets if present and then
// only default to the system bar insets when there are no other options.
- val gesturePadding = when {
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
- insets.getInsets(WindowInsets.Type.systemGestures()).bottom
+ val gesturePadding =
+ when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
+ insets.getInsets(WindowInsets.Type.systemGestures()).bottom
+ }
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
+ @Suppress("DEPRECATION") insets.systemGestureInsets.bottom
+ }
+ else -> 0
}
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
- @Suppress("DEPRECATION")
- insets.systemGestureInsets.bottom
- }
-
- else -> 0
- }
-
updatePadding(
bottom =
- if (gesturePadding != 0)
- gesturePadding
- else
- insets.systemBarInsetsCompat.bottom
- )
+ if (gesturePadding != 0) gesturePadding else insets.systemBarInsetsCompat.bottom)
return insets
}
@@ -91,23 +83,15 @@ class PlaybackBarView @JvmOverloads constructor(
viewLifecycleOwner: LifecycleOwner
) {
setOnLongClickListener {
- playbackModel.song.value?.let { song ->
- detailModel.navToItem(song)
- }
+ playbackModel.song.value?.let { song -> detailModel.navToItem(song) }
true
}
- binding.playbackSkipPrev?.setOnClickListener {
- playbackModel.skipPrev()
- }
+ binding.playbackSkipPrev?.setOnClickListener { playbackModel.skipPrev() }
- binding.playbackPlayPause.setOnClickListener {
- playbackModel.invertPlayingStatus()
- }
+ binding.playbackPlayPause.setOnClickListener { playbackModel.invertPlayingStatus() }
- binding.playbackSkipNext?.setOnClickListener {
- playbackModel.skipNext()
- }
+ binding.playbackSkipNext?.setOnClickListener { playbackModel.skipNext() }
binding.playbackPlayPause.isActivated = playbackModel.isPlaying.value!!
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt
index 4aea26ac2..97ce3dc20 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackButton.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.playback
import android.content.Context
@@ -14,20 +31,19 @@ import org.oxycblt.auxio.util.getDrawableSafe
/**
* An [AppCompatImageButton] designed for the buttons used in the playback display.
*
- * Auxio's playback buttons have never followed the typical 24dp icon size that all
- * other UI elements do, mostly because those icons just look bad at that size with
- * all the gobs of whitespace surrounding them. So, this view resizes the icons to a
- * fixed 32dp in a way that doesn't require a whole new icon set.
+ * Auxio's playback buttons have never followed the typical 24dp icon size that all other UI
+ * elements do, mostly because those icons just look bad at that size with all the gobs of
+ * whitespace surrounding them. So, this view resizes the icons to a fixed 32dp in a way that
+ * doesn't require a whole new icon set.
*
- * This view also enables use of an "indicator", which is a dot that can denote when a
- * button is active. This is useful for the shuffle/loop buttons, as at times highlighting
- * them is not enough to differentiate them.
+ * This view also enables use of an "indicator", which is a dot that can denote when a button is
+ * active. This is useful for the shuffle/loop buttons, as at times highlighting them is not enough
+ * to differentiate them.
*/
-class PlaybackButton @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : AppCompatImageButton(context, attrs, defStyleAttr) {
+class PlaybackButton
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ AppCompatImageButton(context, attrs, defStyleAttr) {
private val iconSize = context.getDimenSizeSafe(R.dimen.size_playback_icon)
private val centerMatrix = Matrix()
private val matrixSrc = RectF()
@@ -55,31 +71,35 @@ class PlaybackButton @JvmOverloads constructor(
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- imageMatrix = centerMatrix.apply {
- reset()
- drawable?.let { drawable ->
- // Android is too good to allow us to set a fixed image size, so we instead need
- // to define a matrix to scale an image directly.
+ imageMatrix =
+ centerMatrix.apply {
+ reset()
+ drawable?.let { drawable ->
+ // Android is too good to allow us to set a fixed image size, so we instead need
+ // to define a matrix to scale an image directly.
- // First scale the icon up to the desired size.
- matrixSrc.set(0f, 0f, drawable.intrinsicWidth.toFloat(), drawable.intrinsicHeight.toFloat())
- matrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat())
- centerMatrix.setRectToRect(matrixSrc, matrixDst, Matrix.ScaleToFit.CENTER)
+ // First scale the icon up to the desired size.
+ matrixSrc.set(
+ 0f,
+ 0f,
+ drawable.intrinsicWidth.toFloat(),
+ drawable.intrinsicHeight.toFloat())
+ matrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat())
+ centerMatrix.setRectToRect(matrixSrc, matrixDst, Matrix.ScaleToFit.CENTER)
- // Then actually center it into the icon, which the previous call does not actually do.
- centerMatrix.postTranslate(
- (measuredWidth - iconSize) / 2f, (measuredHeight - iconSize) / 2f
- )
+ // Then actually center it into the icon, which the previous call does not
+ // actually do.
+ centerMatrix.postTranslate(
+ (measuredWidth - iconSize) / 2f, (measuredHeight - iconSize) / 2f)
+ }
}
- }
// Put the indicator right below the icon.
val x = (measuredWidth - indicatorDrawable.intrinsicWidth) / 2
val y = ((measuredHeight - iconSize) / 2) + iconSize
indicatorDrawable.bounds.set(
- x, y, x + indicatorDrawable.intrinsicWidth, y + indicatorDrawable.intrinsicHeight
- )
+ x, y, x + indicatorDrawable.intrinsicWidth, y + indicatorDrawable.intrinsicHeight)
}
override fun onDraw(canvas: Canvas) {
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
index 5e665e8f1..48296ac77 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback
import android.os.Bundle
@@ -38,8 +37,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
/**
* A [Fragment] that displays more information about the song, along with more media controls.
* Instantiation is done by the navigation component, **do not instantiate this fragment manually.**
- * @author OxygenCobalt
- * TODO: Handle RTL correctly in the playback buttons
+ * @author OxygenCobalt TODO: Handle RTL correctly in the playback buttons
*/
class PlaybackFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()
@@ -66,18 +64,13 @@ class PlaybackFragment : Fragment() {
binding.root.setOnApplyWindowInsetsListener { _, insets ->
val bars = insets.systemBarInsetsCompat
- binding.root.updatePadding(
- top = bars.top,
- bottom = bars.bottom
- )
+ binding.root.updatePadding(top = bars.top, bottom = bars.bottom)
insets
}
binding.playbackToolbar.apply {
- setNavigationOnClickListener {
- navigateUp()
- }
+ setNavigationOnClickListener { navigateUp() }
setOnMenuItemClickListener { item ->
if (item.itemId == R.id.action_queue) {
@@ -96,9 +89,7 @@ class PlaybackFragment : Fragment() {
binding.playbackSeekBar.onConfirmListener = playbackModel::setPosition
// Abuse the play/pause FAB (see style definition for more info)
- binding.playbackPlayPause.post {
- binding.playbackPlayPause.stateListAnimator = null
- }
+ binding.playbackPlayPause.post { binding.playbackPlayPause.stateListAnimator = null }
// --- VIEWMODEL SETUP --
@@ -114,8 +105,8 @@ class PlaybackFragment : Fragment() {
}
playbackModel.parent.observe(viewLifecycleOwner) { parent ->
- binding.playbackToolbar.subtitle = parent?.resolvedName
- ?: getString(R.string.lbl_all_songs)
+ binding.playbackToolbar.subtitle =
+ parent?.resolvedName ?: getString(R.string.lbl_all_songs)
}
playbackModel.isShuffling.observe(viewLifecycleOwner) { isShuffling ->
@@ -123,11 +114,12 @@ class PlaybackFragment : Fragment() {
}
playbackModel.loopMode.observe(viewLifecycleOwner) { loopMode ->
- val resId = when (loopMode) {
- LoopMode.NONE, null -> R.drawable.ic_loop
- LoopMode.ALL -> R.drawable.ic_loop_on
- LoopMode.TRACK -> R.drawable.ic_loop_one
- }
+ val resId =
+ when (loopMode) {
+ LoopMode.NONE, null -> R.drawable.ic_loop
+ LoopMode.ALL -> R.drawable.ic_loop_on
+ LoopMode.TRACK -> R.drawable.ic_loop_one
+ }
binding.playbackLoop.apply {
isActivated = loopMode != LoopMode.NONE
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt
index b458553ac..fd4ccb88d 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackLayout.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.playback
import android.content.Context
@@ -19,6 +36,9 @@ import androidx.core.view.isInvisible
import androidx.customview.widget.ViewDragHelper
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.shape.MaterialShapeDrawable
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.DetailViewModel
@@ -32,31 +52,30 @@ import org.oxycblt.auxio.util.pxOfDp
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
import org.oxycblt.auxio.util.stateList
import org.oxycblt.auxio.util.systemBarInsetsCompat
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.min
/**
- * This layout handles pretty much every aspect of the playback UI flow, notably the playback
- * bar and it's ability to slide up into the playback view. It's a blend of Hai Zhang's
+ * This layout handles pretty much every aspect of the playback UI flow, notably the playback bar
+ * and it's ability to slide up into the playback view. It's a blend of Hai Zhang's
* PersistentBarLayout and Umano's SlidingUpPanelLayout, albeit heavily minified to remove
* extraneous use cases and updated to support the latest SDK level and androidx tools.
*
* **Note:** If you want to adapt this layout into your own app. Good luck. This layout has been
- * reduced to Auxio's use case in particular and is really hard to understand since it has a ton
- * of state and view magic. I tried my best to document it, but it's probably not the most friendly
- * or extendable. You have been warned.
+ * reduced to Auxio's use case in particular and is really hard to understand since it has a ton of
+ * state and view magic. I tried my best to document it, but it's probably not the most friendly or
+ * extendable. You have been warned.
*
- * @author OxygenCobalt (With help from Umano and Hai Zhang)
- * TODO: Find a better way to handle PlaybackFragment in general (navigation, creation)
+ * @author OxygenCobalt (With help from Umano and Hai Zhang) TODO: Find a better way to handle
+ * PlaybackFragment in general (navigation, creation)
*/
-class PlaybackLayout @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyle: Int = 0
-) : ViewGroup(context, attrs, defStyle) {
+class PlaybackLayout
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
+ ViewGroup(context, attrs, defStyle) {
private enum class PanelState {
- EXPANDED, COLLAPSED, HIDDEN, DRAGGING
+ EXPANDED,
+ COLLAPSED,
+ HIDDEN,
+ DRAGGING
}
private lateinit var contentView: View
@@ -67,20 +86,19 @@ class PlaybackLayout @JvmOverloads constructor(
private val playbackContainerBg: MaterialShapeDrawable
private val playbackFragment = PlaybackFragment()
- /**
- * The drag helper that animates and dispatches drag events to the panels.
- */
- private val dragHelper = ViewDragHelper.create(this, DragHelperCallback()).apply {
- minVelocity = MIN_FLING_VEL * resources.displayMetrics.density
- }
+ /** The drag helper that animates and dispatches drag events to the panels. */
+ private val dragHelper =
+ ViewDragHelper.create(this, DragHelperCallback()).apply {
+ minVelocity = MIN_FLING_VEL * resources.displayMetrics.density
+ }
/**
- * The current window insets.
- * Important since this layout must play a long with Auxio's edge-to-edge functionality.
+ * The current window insets. Important since this layout must play a long with Auxio's
+ * edge-to-edge functionality.
*/
private var lastInsets: WindowInsets? = null
- /** The current panel state. Can be [PanelState.DRAGGING]*/
+ /** The current panel state. Can be [PanelState.DRAGGING] */
private var panelState = INIT_PANEL_STATE
/** The last panel state before a drag event began. */
@@ -90,10 +108,8 @@ class PlaybackLayout @JvmOverloads constructor(
private var panelRange = 0
/**
- * The relative offset of this panel as a percentage of [panelRange].
- * A value of 1 means a fully expanded panel.
- * A value of 0 means a collapsed panel.
- * A value below 0 means a hidden panel.
+ * The relative offset of this panel as a percentage of [panelRange]. A value of 1 means a fully
+ * expanded panel. A value of 0 means a collapsed panel. A value below 0 means a hidden panel.
*/
private var panelOffset = 0f
@@ -105,88 +121,96 @@ class PlaybackLayout @JvmOverloads constructor(
private val elevationNormal = context.getDimenSafe(R.dimen.elevation_normal)
/** See [isDragging] */
- private val dragStateField = ViewDragHelper::class.java.getDeclaredField("mDragState").apply {
- isAccessible = true
- }
+ private val dragStateField =
+ ViewDragHelper::class.java.getDeclaredField("mDragState").apply { isAccessible = true }
init {
setWillNotDraw(false)
// Set up our playback views. Doing this allows us to abstract away the implementation
// of these views from the user of this layout [MainFragment].
- playbackContainerView = FrameLayout(context).apply {
- id = R.id.playback_container
+ playbackContainerView =
+ FrameLayout(context).apply {
+ id = R.id.playback_container
- isClickable = true
- isFocusable = false
- isFocusableInTouchMode = false
+ isClickable = true
+ isFocusable = false
+ isFocusableInTouchMode = false
- playbackContainerBg = MaterialShapeDrawable.createWithElevationOverlay(context).apply {
- fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList
- elevation = context.pxOfDp(elevationNormal).toFloat()
+ playbackContainerBg =
+ MaterialShapeDrawable.createWithElevationOverlay(context).apply {
+ fillColor = context.getAttrColorSafe(R.attr.colorSurface).stateList
+ elevation = context.pxOfDp(elevationNormal).toFloat()
+ }
+
+ // The way we fade out the elevation overlay is not by actually reducing the
+ // elevation
+ // but by fading out the background drawable itself. To be safe, we apply this
+ // background drawable to a layer list with another colorSurface shape drawable,
+ // just
+ // in case weird things happen if background drawable is completely transparent.
+ background =
+ (context.getDrawableSafe(R.drawable.ui_panel_bg) as LayerDrawable).apply {
+ setDrawableByLayerId(R.id.panel_overlay, playbackContainerBg)
+ }
+
+ disableDropShadowCompat()
}
- // The way we fade out the elevation overlay is not by actually reducing the elevation
- // but by fading out the background drawable itself. To be safe, we apply this
- // background drawable to a layer list with another colorSurface shape drawable, just
- // in case weird things happen if background drawable is completely transparent.
- background = (context.getDrawableSafe(R.drawable.ui_panel_bg) as LayerDrawable).apply {
- setDrawableByLayerId(R.id.panel_overlay, playbackContainerBg)
- }
+ playbackBarView =
+ PlaybackBarView(context).apply {
+ id = R.id.playback_bar
- disableDropShadowCompat()
- }
+ playbackContainerView.addView(this)
- playbackBarView = PlaybackBarView(context).apply {
- id = R.id.playback_bar
+ (layoutParams as FrameLayout.LayoutParams).apply {
+ width = LayoutParams.MATCH_PARENT
+ height = LayoutParams.WRAP_CONTENT
+ gravity = Gravity.TOP
+ }
- playbackContainerView.addView(this)
-
- (layoutParams as FrameLayout.LayoutParams).apply {
- width = LayoutParams.MATCH_PARENT
- height = LayoutParams.WRAP_CONTENT
- gravity = Gravity.TOP
- }
-
- // The bar view if clicked will expand into the full panel
- setOnClickListener {
- if (canSlide && panelState != PanelState.EXPANDED) {
- applyState(PanelState.EXPANDED)
+ // The bar view if clicked will expand into the full panel
+ setOnClickListener {
+ if (canSlide && panelState != PanelState.EXPANDED) {
+ applyState(PanelState.EXPANDED)
+ }
}
}
- }
- playbackPanelView = FrameLayout(context).apply {
- playbackContainerView.addView(this)
+ playbackPanelView =
+ FrameLayout(context).apply {
+ playbackContainerView.addView(this)
- (layoutParams as FrameLayout.LayoutParams).apply {
- width = LayoutParams.MATCH_PARENT
- height = LayoutParams.MATCH_PARENT
- gravity = Gravity.CENTER
+ (layoutParams as FrameLayout.LayoutParams).apply {
+ width = LayoutParams.MATCH_PARENT
+ height = LayoutParams.MATCH_PARENT
+ gravity = Gravity.CENTER
+ }
+
+ id = R.id.playback_panel
+
+ // Make sure we add our fragment to this view. This is actually a replace operation
+ // since we don't want to stack fragments but we can't ensure that this view doesn't
+ // already have a fragment attached.
+ try {
+ (context as AppCompatActivity)
+ .supportFragmentManager
+ .beginTransaction()
+ .replace(R.id.playback_panel, playbackFragment)
+ .commit()
+ } catch (e: Exception) {
+ // Band-aid to stop the app crashing if we have to swap out the content view
+ // without warning (which we have to do sometimes because android is the worst
+ // thing ever)
+ }
}
-
- id = R.id.playback_panel
-
- // Make sure we add our fragment to this view. This is actually a replace operation
- // since we don't want to stack fragments but we can't ensure that this view doesn't
- // already have a fragment attached.
- try {
- (context as AppCompatActivity).supportFragmentManager.beginTransaction()
- .replace(R.id.playback_panel, playbackFragment)
- .commit()
- } catch (e: Exception) {
- // Band-aid to stop the app crashing if we have to swap out the content view
- // without warning (which we have to do sometimes because android is the worst
- // thing ever)
- }
- }
}
// / --- CONTROL METHODS ---
/**
- * Update the song that this layout is showing. This will be reflected in the compact view
- * at the bottom of the screen.
+ * Update the song that this layout is showing. This will be reflected in the compact view at
+ * the bottom of the screen.
*/
fun setup(
playbackModel: PlaybackViewModel,
@@ -195,9 +219,7 @@ class PlaybackLayout @JvmOverloads constructor(
) {
setSong(playbackModel.song.value)
- playbackModel.song.observe(viewLifecycleOwner) { song ->
- setSong(song)
- }
+ playbackModel.song.observe(viewLifecycleOwner) { song -> setSong(song) }
playbackBarView.setup(playbackModel, detailModel, viewLifecycleOwner)
}
@@ -243,7 +265,8 @@ class PlaybackLayout @JvmOverloads constructor(
}
if (!isLaidOut) {
- // Not laid out, just apply the state and let the measure + layout steps apply it for us.
+ // Not laid out, just apply the state and let the measure + layout steps apply it for
+ // us.
setPanelStateInternal(state)
} else {
// We are laid out. In this case we actually animate to our desired target.
@@ -293,11 +316,12 @@ class PlaybackLayout @JvmOverloads constructor(
if (!isLaidOut) {
// This is our first layout, so make sure we know what offset we should work with
// before we measure our content
- panelOffset = when (panelState) {
- PanelState.EXPANDED -> 1.0f
- PanelState.HIDDEN -> computePanelOffset(measuredHeight)
- else -> 0f
- }
+ panelOffset =
+ when (panelState) {
+ PanelState.EXPANDED -> 1.0f
+ PanelState.HIDDEN -> computePanelOffset(measuredHeight)
+ else -> 0f
+ }
updatePanelTransition()
}
@@ -315,9 +339,8 @@ class PlaybackLayout @JvmOverloads constructor(
// Note that these views will always be a fixed MATCH_PARENT. This is intentional,
// as it reduces the logic we have to deal with regarding WRAP_CONTENT views.
val contentWidthSpec = MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY)
- val contentHeightSpec = MeasureSpec.makeMeasureSpec(
- measuredHeight - barHeightAdjusted, MeasureSpec.EXACTLY
- )
+ val contentHeightSpec =
+ MeasureSpec.makeMeasureSpec(measuredHeight - barHeightAdjusted, MeasureSpec.EXACTLY)
contentView.measure(contentWidthSpec, contentHeightSpec)
}
@@ -330,8 +353,7 @@ class PlaybackLayout @JvmOverloads constructor(
0,
panelTop,
playbackContainerView.measuredWidth,
- playbackContainerView.measuredHeight + panelTop
- )
+ playbackContainerView.measuredHeight + panelTop)
layoutContent()
}
@@ -352,9 +374,7 @@ class PlaybackLayout @JvmOverloads constructor(
canvas.clipRect(tRect)
}
- return super.drawChild(canvas, child, drawingTime).also {
- canvas.restoreToCount(save)
- }
+ return super.drawChild(canvas, child, drawingTime).also { canvas.restoreToCount(save) }
}
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
@@ -369,8 +389,8 @@ class PlaybackLayout @JvmOverloads constructor(
}
/**
- * Apply window insets to the content views in this layouts. This is done separately as at
- * times we want to re-inset the content views but not re-inset the bar view.
+ * Apply window insets to the content views in this layouts. This is done separately as at times
+ * we want to re-inset the content views but not re-inset the bar view.
*/
private fun applyContentWindowInsets() {
val insets = lastInsets
@@ -379,9 +399,7 @@ class PlaybackLayout @JvmOverloads constructor(
}
}
- /**
- * Adjust window insets to line up with the panel
- */
+ /** Adjust window insets to line up with the panel */
private fun adjustInsets(insets: WindowInsets): WindowInsets {
// We kind to do a reverse-measure to figure out how we should inset this view.
// Find how much space is lost by the panel and then combine that with the
@@ -390,21 +408,20 @@ class PlaybackLayout @JvmOverloads constructor(
val consumedByPanel = computePanelTopPosition(panelOffset) - measuredHeight
val adjustedBottomInset = (consumedByPanel + bars.bottom).coerceAtLeast(0)
return insets.replaceSystemBarInsetsCompat(
- bars.left, bars.top, bars.right, adjustedBottomInset
- )
+ bars.left, bars.top, bars.right, adjustedBottomInset)
}
- override fun onSaveInstanceState(): Parcelable = Bundle().apply {
- putParcelable("superState", super.onSaveInstanceState())
- putSerializable(
- KEY_PANEL_STATE,
- if (panelState != PanelState.DRAGGING) {
- panelState
- } else {
- lastIdlePanelState
- }
- )
- }
+ override fun onSaveInstanceState(): Parcelable =
+ Bundle().apply {
+ putParcelable("superState", super.onSaveInstanceState())
+ putSerializable(
+ KEY_PANEL_STATE,
+ if (panelState != PanelState.DRAGGING) {
+ panelState
+ } else {
+ lastIdlePanelState
+ })
+ }
override fun onRestoreInstanceState(state: Parcelable) {
if (state is Bundle) {
@@ -425,13 +442,14 @@ class PlaybackLayout @JvmOverloads constructor(
return if (!canSlide) {
super.onTouchEvent(ev)
- } else try {
- dragHelper.processTouchEvent(ev)
- true
- } catch (ex: Exception) {
- // Ignore the pointer out of range exception
- false
- }
+ } else
+ try {
+ dragHelper.processTouchEvent(ev)
+ true
+ } catch (ex: Exception) {
+ // Ignore the pointer out of range exception
+ false
+ }
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
@@ -454,10 +472,10 @@ class PlaybackLayout @JvmOverloads constructor(
return false
}
}
-
MotionEvent.ACTION_MOVE -> {
val pointerUnder = playbackContainerView.isUnder(ev.x.toInt(), ev.y.toInt())
- val motionUnder = playbackContainerView.isUnder(initMotionX.toInt(), initMotionY.toInt())
+ val motionUnder =
+ playbackContainerView.isUnder(initMotionX.toInt(), initMotionY.toInt())
if (!(pointerUnder || motionUnder) || ady > dragSlop && adx > ady) {
// Pointer has moved beyond our control, do not intercept this event
@@ -465,7 +483,6 @@ class PlaybackLayout @JvmOverloads constructor(
return false
}
}
-
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP ->
if (dragHelper.isDragging) {
// Stopped pressing while we were dragging, let the drag helper handle it
@@ -504,11 +521,12 @@ class PlaybackLayout @JvmOverloads constructor(
get() {
// We can't grab the drag state outside of a callback, but that's stupid and I don't
// want to vendor ViewDragHelper so I just do reflection instead.
- val state = try {
- dragStateField.get(this)
- } catch (e: Exception) {
- ViewDragHelper.STATE_IDLE
- }
+ val state =
+ try {
+ dragStateField.get(this)
+ } catch (e: Exception) {
+ ViewDragHelper.STATE_IDLE
+ }
return state == ViewDragHelper.STATE_DRAGGING
}
@@ -524,9 +542,9 @@ class PlaybackLayout @JvmOverloads constructor(
}
/**
- * Do the nice view animations that occur whenever we slide up the playback panel.
- * The way I transition is largely inspired by Android 12's notification panel, with the
- * compact view fading out completely before the panel view fades in.
+ * Do the nice view animations that occur whenever we slide up the playback panel. The way I
+ * transition is largely inspired by Android 12's notification panel, with the compact view
+ * fading out completely before the panel view fades in.
*/
private fun updatePanelTransition() {
val ratio = max(panelOffset, 0f)
@@ -566,8 +584,7 @@ class PlaybackLayout @JvmOverloads constructor(
params.leftMargin,
(bars.top * halfOutRatio).toInt(),
params.rightMargin,
- params.bottomMargin
- )
+ params.bottomMargin)
// Poke the layout only when we changed something
if (params.topMargin != oldTopMargin) {
@@ -592,9 +609,9 @@ class PlaybackLayout @JvmOverloads constructor(
private fun smoothSlideTo(offset: Float) {
logD("Smooth sliding to $offset")
- val okay = dragHelper.smoothSlideViewTo(
- playbackContainerView, playbackContainerView.left, computePanelTopPosition(offset)
- )
+ val okay =
+ dragHelper.smoothSlideViewTo(
+ playbackContainerView, playbackContainerView.left, computePanelTopPosition(offset))
if (okay) {
postInvalidateOnAnimation()
@@ -621,7 +638,6 @@ class PlaybackLayout @JvmOverloads constructor(
setPanelStateInternal(PanelState.HIDDEN)
playbackContainerView.visibility = INVISIBLE
}
-
else -> setPanelStateInternal(PanelState.EXPANDED)
}
}
@@ -658,16 +674,17 @@ class PlaybackLayout @JvmOverloads constructor(
}
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
- val newOffset = when {
- // Swipe Up -> Expand to top
- yvel < 0 -> 1f
- // Swipe down -> Collapse to bottom
- yvel > 0 -> 0f
- // No velocity, far enough from middle to expand to top
- panelOffset >= 0.5f -> 1f
- // Collapse to bottom
- else -> 0f
- }
+ val newOffset =
+ when {
+ // Swipe Up -> Expand to top
+ yvel < 0 -> 1f
+ // Swipe down -> Collapse to bottom
+ yvel > 0 -> 0f
+ // No velocity, far enough from middle to expand to top
+ panelOffset >= 0.5f -> 1f
+ // Collapse to bottom
+ else -> 0f
+ }
dragHelper.settleCapturedViewAt(releasedChild.left, computePanelTopPosition(newOffset))
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt
index 6b7002141..cca6bff89 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackSeekBar.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackSeeker.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback
import android.annotation.SuppressLint
@@ -33,20 +32,22 @@ import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.stateList
/**
- * A custom view that bundles together a seekbar with a current duration and a total duration.
- * The sub-views are specifically laid out so that the seekbar has an adequate touch height while
- * still not having gobs of whitespace everywhere.
- * TODO: Add smooth seeking [i.e seeking in sub-second values]
+ * A custom view that bundles together a seekbar with a current duration and a total duration. The
+ * sub-views are specifically laid out so that the seekbar has an adequate touch height while still
+ * not having gobs of whitespace everywhere. TODO: Add smooth seeking [i.e seeking in sub-second
+ * values]
* @author OxygenCobalt
*/
@SuppressLint("RestrictedApi")
-class PlaybackSeekBar @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleRes: Int = 0
-) : ConstraintLayout(context, attrs, defStyleRes), Slider.OnChangeListener, Slider.OnSliderTouchListener {
+class PlaybackSeekBar
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0) :
+ ConstraintLayout(context, attrs, defStyleRes),
+ Slider.OnChangeListener,
+ Slider.OnSliderTouchListener {
private val binding = ViewSeekBarBinding.inflate(context.inflater, this, true)
- private val isSeeking: Boolean get() = binding.playbackDurationCurrent.isActivated
+ private val isSeeking: Boolean
+ get() = binding.playbackDurationCurrent.isActivated
var onConfirmListener: ((Long) -> Unit)? = null
@@ -55,9 +56,10 @@ class PlaybackSeekBar @JvmOverloads constructor(
binding.seekBar.addOnSliderTouchListener(this)
// Override the inactive color so that it lines up with the playback progress bar.
- binding.seekBar.trackInactiveTintList = MaterialColors.compositeARGBWithAlpha(
- context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt()
- ).stateList
+ binding.seekBar.trackInactiveTintList =
+ MaterialColors.compositeARGBWithAlpha(
+ context.getAttrColorSafe(R.attr.colorSecondary), (255 * 0.2).toInt())
+ .stateList
}
fun setProgress(seconds: Long) {
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
index 93fd20cac..14f30667e 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackViewModel.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback
import android.content.Context
@@ -41,13 +40,14 @@ import org.oxycblt.auxio.util.logE
/**
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
*
- * **PLEASE Use this instead of [PlaybackStateManager], UI's are extremely volatile and this provides
- * an interface that properly sanitizes input and abstracts functions unlike the master class.**
+ * **PLEASE Use this instead of [PlaybackStateManager], UI's are extremely volatile and this
+ * provides an interface that properly sanitizes input and abstracts functions unlike the master
+ * class.**
* @author OxygenCobalt
*
- * TODO: Completely rework this module to support the new music rescan system,
- * proper android auto and external exposing, and so on.
- * - DO NOT REWRITE IT! THAT'S BAD AND WILL PROBABLY RE-INTRODUCE A TON OF BUGS.
+ * TODO: Completely rework this module to support the new music rescan system, proper android auto
+ * and external exposing, and so on.
+ * - DO NOT REWRITE IT! THAT'S BAD AND WILL PROBABLY RE-INTRODUCE A TON OF BUGS.
*/
class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// Playback
@@ -68,21 +68,29 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
private var mIntentUri: Uri? = null
/** The current song. */
- val song: LiveData get() = mSong
+ val song: LiveData
+ get() = mSong
/** The current model that is being played from, such as an [Album] or [Artist] */
- val parent: LiveData get() = mParent
+ val parent: LiveData
+ get() = mParent
- val isPlaying: LiveData get() = mIsPlaying
- val isShuffling: LiveData get() = mIsShuffling
+ val isPlaying: LiveData
+ get() = mIsPlaying
+ val isShuffling: LiveData
+ get() = mIsShuffling
/** The current repeat mode, see [LoopMode] for more information */
- val loopMode: LiveData get() = mLoopMode
+ val loopMode: LiveData
+ get() = mLoopMode
/** The current playback position, in seconds */
- val position: LiveData get() = mPosition
+ val position: LiveData
+ get() = mPosition
/** The queue, without the previous items. */
- val nextUp: LiveData> get() = mNextUp
+ val nextUp: LiveData>
+ get() = mNextUp
/** The current [PlaybackMode] that also determines the queue */
- val playbackMode: LiveData get() = mMode
+ val playbackMode: LiveData
+ get() = mMode
private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
@@ -102,8 +110,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// --- PLAYING FUNCTIONS ---
/**
- * Play a [song] with the [mode] specified. [mode] will default to the preferred song
- * playback mode of the user if not specified.
+ * Play a [song] with the [mode] specified. [mode] will default to the preferred song playback
+ * mode of the user if not specified.
*/
fun playSong(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) {
playbackManager.playSong(song, mode)
@@ -152,8 +160,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
}
/**
- * Play using a file [uri].
- * This will not play instantly during the initial startup sequence.
+ * Play using a file [uri]. This will not play instantly during the initial startup sequence.
*/
fun playWithUri(uri: Uri, context: Context) {
// Check if everything is already running to run the URI play
@@ -166,54 +173,41 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
}
}
- /**
- * Play with a file URI.
- * This is called after [playWithUri] once its deemed safe to do so.
- */
+ /** Play with a file URI. This is called after [playWithUri] once its deemed safe to do so. */
private fun playWithUriInternal(uri: Uri, context: Context) {
logD("Playing with uri $uri")
val musicStore = MusicStore.maybeGetInstance() ?: return
- musicStore.findSongForUri(uri, context.contentResolver)?.let { song ->
- playSong(song)
- }
+ musicStore.findSongForUri(uri, context.contentResolver)?.let { song -> playSong(song) }
}
- /**
- * Shuffle all songs
- */
+ /** Shuffle all songs */
fun shuffleAll() {
playbackManager.shuffleAll()
}
// --- POSITION FUNCTIONS ---
- /**
- * Update the position and push it to [PlaybackStateManager]
- */
+ /** Update the position and push it to [PlaybackStateManager] */
fun setPosition(progress: Long) {
playbackManager.seekTo((progress * 1000))
}
// --- QUEUE FUNCTIONS ---
- /**
- * Skip to the next song.
- */
+ /** Skip to the next song. */
fun skipNext() {
playbackManager.next()
}
- /**
- * Skip to the previous song.
- */
+ /** Skip to the previous song. */
fun skipPrev() {
playbackManager.prev()
}
/**
- * Remove a queue item using it's recyclerview adapter index. If the indices are valid,
- * [apply] is called just before the change is committed so that the adapter can be updated.
+ * Remove a queue item using it's recyclerview adapter index. If the indices are valid, [apply]
+ * is called just before the change is committed so that the adapter can be updated.
*/
fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) {
val index = adapterIndex + (playbackManager.queue.size - mNextUp.value!!.size)
@@ -223,8 +217,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
}
}
/**
- * Move queue items using their recyclerview adapter indices. If the indices are valid,
- * [apply] is called just before the change is committed so that the adapter can be updated.
+ * Move queue items using their recyclerview adapter indices. If the indices are valid, [apply]
+ * is called just before the change is committed so that the adapter can be updated.
*/
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean {
val delta = (playbackManager.queue.size - mNextUp.value!!.size)
@@ -239,53 +233,39 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
return false
}
- /**
- * Add a [Song] to the top of the queue.
- */
+ /** Add a [Song] to the top of the queue. */
fun playNext(song: Song) {
playbackManager.playNext(song)
}
- /**
- * Add an [Album] to the top of the queue.
- */
+ /** Add an [Album] to the top of the queue. */
fun playNext(album: Album) {
playbackManager.playNext(settingsManager.detailAlbumSort.sortAlbum(album))
}
-/**
- * Add a [Song] to the end of the queue.
- */
+ /** Add a [Song] to the end of the queue. */
fun addToQueue(song: Song) {
playbackManager.addToQueue(song)
}
- /**
- * Add an [Album] to the end of the queue.
- */
+ /** Add an [Album] to the end of the queue. */
fun addToQueue(album: Album) {
playbackManager.addToQueue(settingsManager.detailAlbumSort.sortAlbum(album))
}
-// --- STATUS FUNCTIONS ---
+ // --- STATUS FUNCTIONS ---
- /**
- * Flip the playing status, e.g from playing to paused
- */
+ /** Flip the playing status, e.g from playing to paused */
fun invertPlayingStatus() {
playbackManager.setPlaying(!playbackManager.isPlaying)
}
- /**
- * Flip the shuffle status, e.g from on to off. Will keep song by default.
- */
+ /** Flip the shuffle status, e.g from on to off. Will keep song by default. */
fun invertShuffleStatus() {
playbackManager.setShuffling(!playbackManager.isShuffling, true)
}
- /**
- * Increment the loop status, e.g from off to loop once
- */
+ /** Increment the loop status, e.g from off to loop once */
fun incrementLoopStatus() {
playbackManager.setLoopMode(playbackManager.loopMode.increment())
}
@@ -293,8 +273,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// --- SAVE/RESTORE FUNCTIONS ---
/**
- * Force save the current [PlaybackStateManager] state to the database.
- * Called by SettingsListFragment.
+ * Force save the current [PlaybackStateManager] state to the database. Called by
+ * SettingsListFragment.
*/
fun savePlaybackState(context: Context, onDone: () -> Unit) {
viewModelScope.launch {
@@ -320,15 +300,13 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
playbackManager.markRestored()
} else if (!playbackManager.isRestored) {
// Otherwise just restore
- viewModelScope.launch {
- playbackManager.restoreFromDatabase(context)
- }
+ viewModelScope.launch { playbackManager.restoreFromDatabase(context) }
}
}
/**
- * Attempt to restore the current playback state from an existing
- * [PlaybackStateManager] instance.
+ * Attempt to restore the current playback state from an existing [PlaybackStateManager]
+ * instance.
*/
private fun restorePlaybackState() {
logD("Attempting to restore playback state")
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt
index 7c362c5fb..6e9e5a8fd 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * QueueAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.queue
import android.annotation.SuppressLint
@@ -47,9 +46,8 @@ import org.oxycblt.auxio.util.stateList
* @param touchHelper The [ItemTouchHelper] ***containing*** [QueueDragCallback] to be used
* @author OxygenCobalt
*/
-class QueueAdapter(
- private val touchHelper: ItemTouchHelper
-) : RecyclerView.Adapter() {
+class QueueAdapter(private val touchHelper: ItemTouchHelper) :
+ RecyclerView.Adapter() {
private var data = mutableListOf- ()
private var listDiffer = AsyncListDiffer(this, DiffCallback())
@@ -60,16 +58,14 @@ class QueueAdapter(
is Song -> QUEUE_SONG_ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
-
else -> -1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
- QUEUE_SONG_ITEM_TYPE -> QueueSongViewHolder(
- ItemQueueSongBinding.inflate(parent.context.inflater)
- )
+ QUEUE_SONG_ITEM_TYPE ->
+ QueueSongViewHolder(ItemQueueSongBinding.inflate(parent.context.inflater))
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type $viewType")
@@ -86,8 +82,8 @@ class QueueAdapter(
}
/**
- * Submit data using [AsyncListDiffer].
- * **Only use this if you have no idea what changes occurred to the data**
+ * Submit data using [AsyncListDiffer]. **Only use this if you have no idea what changes
+ * occurred to the data**
*/
fun submitList(newData: MutableList
- ) {
if (data != newData) {
@@ -96,39 +92,32 @@ class QueueAdapter(
}
}
- /**
- * Move Items.
- * Used since [submitList] will cause QueueAdapter to freak out.
- */
+ /** Move Items. Used since [submitList] will cause QueueAdapter to freak out. */
fun moveItems(adapterFrom: Int, adapterTo: Int) {
data.add(adapterTo, data.removeAt(adapterFrom))
notifyItemMoved(adapterFrom, adapterTo)
}
- /**
- * Remove an item.
- * Used since [submitList] will cause QueueAdapter to freak out.
- */
+ /** Remove an item. Used since [submitList] will cause QueueAdapter to freak out. */
fun removeItem(adapterIndex: Int) {
data.removeAt(adapterIndex)
notifyItemRemoved(adapterIndex)
}
- /**
- * Generic ViewHolder for a queue song
- */
+ /** Generic ViewHolder for a queue song */
inner class QueueSongViewHolder(
private val binding: ItemQueueSongBinding,
) : BaseViewHolder(binding) {
- val bodyView: View get() = binding.body
- val backgroundView: View get() = binding.background
+ val bodyView: View
+ get() = binding.body
+ val backgroundView: View
+ get() = binding.background
init {
- binding.body.background = MaterialShapeDrawable.createWithElevationOverlay(
- binding.root.context
- ).apply {
- fillColor = (binding.body.background as ColorDrawable).color.stateList
- }
+ binding.body.background =
+ MaterialShapeDrawable.createWithElevationOverlay(binding.root.context).apply {
+ fillColor = (binding.body.background as ColorDrawable).color.stateList
+ }
binding.root.disableDropShadowCompat()
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt
index a010fadf8..3096b2ab8 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * QueueDragCallback.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.queue
import android.graphics.Canvas
@@ -24,19 +23,19 @@ import androidx.core.view.isInvisible
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.shape.MaterialShapeDrawable
-import org.oxycblt.auxio.R
-import org.oxycblt.auxio.playback.PlaybackViewModel
-import org.oxycblt.auxio.util.getDimenSafe
-import org.oxycblt.auxio.util.logD
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
+import org.oxycblt.auxio.R
+import org.oxycblt.auxio.playback.PlaybackViewModel
+import org.oxycblt.auxio.util.getDimenSafe
+import org.oxycblt.auxio.util.logD
/**
* A highly customized [ItemTouchHelper.Callback] that handles the queue system while basically
- * rebuilding most the "Material-y" aspects of an editable list because Google's implementations
- * are hot garbage. This shouldn't have *too many* UI bugs. I hope.
+ * rebuilding most the "Material-y" aspects of an editable list because Google's implementations are
+ * hot garbage. This shouldn't have *too many* UI bugs. I hope.
* @author OxygenCobalt
*/
class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouchHelper.Callback() {
@@ -59,17 +58,14 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
): Int {
// Fix to make QueueFragment scroll slower when an item is scrolled out of bounds.
// Adapted from NewPipe: https://github.com/TeamNewPipe/NewPipe
- val standardSpeed = super.interpolateOutOfBoundsScroll(
- recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll
- )
+ val standardSpeed =
+ super.interpolateOutOfBoundsScroll(
+ recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll)
- val clampedAbsVelocity = max(
- MINIMUM_INITIAL_DRAG_VELOCITY,
- min(
- abs(standardSpeed),
- MAXIMUM_INITIAL_DRAG_VELOCITY
- )
- )
+ val clampedAbsVelocity =
+ max(
+ MINIMUM_INITIAL_DRAG_VELOCITY,
+ min(abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY))
return clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt()
}
@@ -94,12 +90,12 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
val bg = holder.bodyView.background as MaterialShapeDrawable
val elevation = recyclerView.context.getDimenSafe(R.dimen.elevation_small)
- holder.itemView.animate()
+ holder
+ .itemView
+ .animate()
.translationZ(elevation)
.setDuration(100)
- .setUpdateListener {
- bg.elevation = holder.itemView.translationZ
- }
+ .setUpdateListener { bg.elevation = holder.itemView.translationZ }
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
@@ -132,7 +128,9 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
logD("Dropping queue item")
val bg = holder.bodyView.background as MaterialShapeDrawable
- holder.itemView.animate()
+ holder
+ .itemView
+ .animate()
.translationZ(0.0f)
.setDuration(100)
.setUpdateListener { bg.elevation = holder.itemView.translationZ }
@@ -154,9 +152,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
- return playbackModel.moveQueueDataItems(from, to) {
- queueAdapter.moveItems(from, to)
- }
+ return playbackModel.moveQueueDataItems(from, to) { queueAdapter.moveItems(from, to) }
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
@@ -168,8 +164,8 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
override fun isLongPressDragEnabled(): Boolean = false
/**
- * Add the queue adapter to this callback.
- * Done because there's a circular dependency between the two objects
+ * Add the queue adapter to this callback. Done because there's a circular dependency between
+ * the two objects
*/
fun addQueueAdapter(adapter: QueueAdapter) {
queueAdapter = adapter
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt
index 0337c54c7..bf29fcf7e 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * QueueFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.queue
import android.os.Bundle
@@ -54,9 +53,7 @@ class QueueFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
- binding.queueToolbar.setNavigationOnClickListener {
- findNavController().navigateUp()
- }
+ binding.queueToolbar.setNavigationOnClickListener { findNavController().navigateUp() }
binding.queueRecycler.apply {
setHasFixedSize(true)
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt
index b9e336d75..a7ca40828 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * LoopMode.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.state
/**
@@ -23,11 +22,11 @@ package org.oxycblt.auxio.playback.state
* @author OxygenCobalt
*/
enum class LoopMode {
- NONE, ALL, TRACK;
+ NONE,
+ ALL,
+ TRACK;
- /**
- * Increment the LoopMode, e.g from [NONE] to [ALL]
- */
+ /** Increment the LoopMode, e.g from [NONE] to [ALL] */
fun increment(): LoopMode {
return when (this) {
NONE -> ALL
@@ -53,15 +52,12 @@ enum class LoopMode {
private const val INT_ALL = 0xA101
private const val INT_TRACK = 0xA102
- /**
- * Convert an int [constant] into a LoopMode, or null if it isn't valid.
- */
+ /** Convert an int [constant] into a LoopMode, or null if it isn't valid. */
fun fromInt(constant: Int): LoopMode? {
return when (constant) {
INT_NONE -> NONE
INT_ALL -> ALL
INT_TRACK -> TRACK
-
else -> null
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt
index 48924d241..2c49d25de 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackMode.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.state
/**
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt
index 3450096f3..c899c8be3 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackStateDatabase.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.state
import android.content.ContentValues
@@ -32,8 +31,8 @@ import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.queryAll
/**
- * A SQLite database for managing the persistent playback state and queue.
- * Yes. I know Room exists. But that would needlessly bloat my app and has crippling bugs.
+ * A SQLite database for managing the persistent playback state and queue. Yes. I know Room exists.
+ * But that would needlessly bloat my app and has crippling bugs.
* @author OxygenCobalt
*/
class PlaybackStateDatabase(context: Context) :
@@ -58,9 +57,7 @@ class PlaybackStateDatabase(context: Context) :
// --- DATABASE CONSTRUCTION FUNCTIONS ---
- /**
- * Create a table for this database.
- */
+ /** Create a table for this database. */
private fun createTable(database: SQLiteDatabase, tableName: String) {
val command = StringBuilder()
command.append("CREATE TABLE IF NOT EXISTS $tableName(")
@@ -74,11 +71,10 @@ class PlaybackStateDatabase(context: Context) :
database.execSQL(command.toString())
}
- /**
- * Construct a [StateColumns] table
- */
+ /** Construct a [StateColumns] table */
private fun constructStateTable(command: StringBuilder): StringBuilder {
- command.append("${StateColumns.COLUMN_ID} LONG PRIMARY KEY,")
+ command
+ .append("${StateColumns.COLUMN_ID} LONG PRIMARY KEY,")
.append("${StateColumns.COLUMN_SONG_HASH} LONG,")
.append("${StateColumns.COLUMN_POSITION} LONG NOT NULL,")
.append("${StateColumns.COLUMN_PARENT_HASH} LONG,")
@@ -90,11 +86,10 @@ class PlaybackStateDatabase(context: Context) :
return command
}
- /**
- * Construct a [QueueColumns] table
- */
+ /** Construct a [QueueColumns] table */
private fun constructQueueTable(command: StringBuilder): StringBuilder {
- command.append("${QueueColumns.ID} LONG PRIMARY KEY,")
+ command
+ .append("${QueueColumns.ID} LONG PRIMARY KEY,")
.append("${QueueColumns.SONG_HASH} INTEGER NOT NULL,")
.append("${QueueColumns.ALBUM_HASH} INTEGER NOT NULL)")
@@ -126,30 +121,31 @@ class PlaybackStateDatabase(context: Context) :
cursor.moveToFirst()
- val song = cursor.getLongOrNull(songIndex)?.let { id ->
- musicStore.songs.find { it.id == id }
- }
+ val song =
+ cursor.getLongOrNull(songIndex)?.let { id -> musicStore.songs.find { it.id == id } }
val mode = PlaybackMode.fromInt(cursor.getInt(modeIndex)) ?: PlaybackMode.ALL_SONGS
- val parent = cursor.getLongOrNull(parentIndex)?.let { id ->
- when (mode) {
- PlaybackMode.IN_GENRE -> musicStore.genres.find { it.id == id }
- PlaybackMode.IN_ARTIST -> musicStore.artists.find { it.id == id }
- PlaybackMode.IN_ALBUM -> musicStore.albums.find { it.id == id }
- PlaybackMode.ALL_SONGS -> null
+ val parent =
+ cursor.getLongOrNull(parentIndex)?.let { id ->
+ when (mode) {
+ PlaybackMode.IN_GENRE -> musicStore.genres.find { it.id == id }
+ PlaybackMode.IN_ARTIST -> musicStore.artists.find { it.id == id }
+ PlaybackMode.IN_ALBUM -> musicStore.albums.find { it.id == id }
+ PlaybackMode.ALL_SONGS -> null
+ }
}
- }
- state = SavedState(
- song = song,
- position = cursor.getLong(posIndex),
- parent = parent,
- queueIndex = cursor.getInt(indexIndex),
- playbackMode = mode,
- isShuffling = cursor.getInt(shuffleIndex) == 1,
- loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE,
- )
+ state =
+ SavedState(
+ song = song,
+ position = cursor.getLong(posIndex),
+ parent = parent,
+ queueIndex = cursor.getInt(indexIndex),
+ playbackMode = mode,
+ isShuffling = cursor.getInt(shuffleIndex) == 1,
+ loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE,
+ )
logD("Successfully read playback state: $state")
}
@@ -157,9 +153,7 @@ class PlaybackStateDatabase(context: Context) :
return state
}
- /**
- * Clear the previously written [SavedState] and write a new one.
- */
+ /** Clear the previously written [SavedState] and write a new one. */
fun writeState(state: SavedState) {
assertBackgroundThread()
@@ -168,16 +162,17 @@ class PlaybackStateDatabase(context: Context) :
this@PlaybackStateDatabase.logD("Wiped state db")
- val stateData = ContentValues(10).apply {
- put(StateColumns.COLUMN_ID, 0)
- put(StateColumns.COLUMN_SONG_HASH, state.song?.id)
- put(StateColumns.COLUMN_POSITION, state.position)
- put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id)
- put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex)
- put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt())
- put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling)
- put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt())
- }
+ val stateData =
+ ContentValues(10).apply {
+ put(StateColumns.COLUMN_ID, 0)
+ put(StateColumns.COLUMN_SONG_HASH, state.song?.id)
+ put(StateColumns.COLUMN_POSITION, state.position)
+ put(StateColumns.COLUMN_PARENT_HASH, state.parent?.id)
+ put(StateColumns.COLUMN_QUEUE_INDEX, state.queueIndex)
+ put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt())
+ put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling)
+ put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt())
+ }
insert(TABLE_NAME_STATE, null, stateData)
}
@@ -202,9 +197,7 @@ class PlaybackStateDatabase(context: Context) :
while (cursor.moveToNext()) {
musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex))
- ?.let { song ->
- queue.add(song)
- }
+ ?.let { song -> queue.add(song) }
}
}
@@ -213,16 +206,12 @@ class PlaybackStateDatabase(context: Context) :
return queue
}
- /**
- * Write a queue to the database.
- */
+ /** Write a queue to the database. */
fun writeQueue(queue: MutableList) {
assertBackgroundThread()
val database = writableDatabase
- database.transaction {
- delete(TABLE_NAME_QUEUE, null, null)
- }
+ database.transaction { delete(TABLE_NAME_QUEUE, null, null) }
logD("Wiped queue db")
@@ -243,11 +232,12 @@ class PlaybackStateDatabase(context: Context) :
val song = queue[i]
i++
- val itemData = ContentValues(4).apply {
- put(QueueColumns.ID, idStart + i)
- put(QueueColumns.SONG_HASH, song.id)
- put(QueueColumns.ALBUM_HASH, song.album.id)
- }
+ val itemData =
+ ContentValues(4).apply {
+ put(QueueColumns.ID, idStart + i)
+ put(QueueColumns.SONG_HASH, song.id)
+ put(QueueColumns.ALBUM_HASH, song.album.id)
+ }
insert(TABLE_NAME_QUEUE, null, itemData)
}
@@ -295,12 +285,9 @@ class PlaybackStateDatabase(context: Context) :
const val TABLE_NAME_STATE = "playback_state_table"
const val TABLE_NAME_QUEUE = "queue_table"
- @Volatile
- private var INSTANCE: PlaybackStateDatabase? = null
+ @Volatile private var INSTANCE: PlaybackStateDatabase? = null
- /**
- * Get/Instantiate the single instance of [PlaybackStateDatabase].
- */
+ /** Get/Instantiate the single instance of [PlaybackStateDatabase]. */
fun getInstance(context: Context): PlaybackStateDatabase {
val currentInstance = INSTANCE
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
index 348bb2a81..f1c07d8fe 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackStateManager.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.state
import android.content.Context
@@ -35,8 +34,10 @@ import org.oxycblt.auxio.util.logE
* Master class (and possible god object) for the playback state.
*
* This should ***NOT*** be used outside of the playback module.
- * - If you want to use the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel] as it can withstand volatile UIs.
- * - If you want to use the playback state with the ExoPlayer instance or system-side things, use [org.oxycblt.auxio.playback.system.PlaybackService].
+ * - If you want to use the playback state in the UI, use
+ * [org.oxycblt.auxio.playback.PlaybackViewModel] as it can withstand volatile UIs.
+ * - If you want to use the playback state with the ExoPlayer instance or system-side things, use
+ * [org.oxycblt.auxio.playback.system.PlaybackService].
*
* All access should be done with [PlaybackStateManager.getInstance].
* @author OxygenCobalt
@@ -90,27 +91,38 @@ class PlaybackStateManager private constructor() {
private var mHasPlayed = false
/** The currently playing song. Null if there isn't one */
- val song: Song? get() = mSong
+ val song: Song?
+ get() = mSong
/** The parent the queue is based on, null if all_songs */
- val parent: MusicParent? get() = mParent
+ val parent: MusicParent?
+ get() = mParent
/** The current playback progress */
- val position: Long get() = mPosition
+ val position: Long
+ get() = mPosition
/** The current queue determined by [parent] and [playbackMode] */
- val queue: List get() = mQueue
+ val queue: List
+ get() = mQueue
/** The current position in the queue */
- val index: Int get() = mIndex
+ val index: Int
+ get() = mIndex
/** The current [PlaybackMode] */
- val playbackMode: PlaybackMode get() = mPlaybackMode
+ val playbackMode: PlaybackMode
+ get() = mPlaybackMode
/** Whether playback is paused or not */
- val isPlaying: Boolean get() = mIsPlaying
+ val isPlaying: Boolean
+ get() = mIsPlaying
/** Whether the queue is shuffled */
- val isShuffling: Boolean get() = mIsShuffling
+ val isShuffling: Boolean
+ get() = mIsShuffling
/** The current [LoopMode] */
- val loopMode: LoopMode get() = mLoopMode
+ val loopMode: LoopMode
+ get() = mLoopMode
/** Whether this instance has already been restored */
- val isRestored: Boolean get() = mIsRestored
+ val isRestored: Boolean
+ get() = mIsRestored
/** Whether playback has begun in this instance during **PlaybackService's Lifecycle.** */
- val hasPlayed: Boolean get() = mHasPlayed
+ val hasPlayed: Boolean
+ get() = mHasPlayed
private val settingsManager = SettingsManager.getInstance()
@@ -119,16 +131,14 @@ class PlaybackStateManager private constructor() {
private val callbacks = mutableListOf()
/**
- * Add a [PlaybackStateManager.Callback] to this instance.
- * Make sure to remove the callback with [removeCallback] when done.
+ * Add a [PlaybackStateManager.Callback] to this instance. Make sure to remove the callback with
+ * [removeCallback] when done.
*/
fun addCallback(callback: Callback) {
callbacks.add(callback)
}
- /**
- * Remove a [PlaybackStateManager.Callback] bound to this instance.
- */
+ /** Remove a [PlaybackStateManager.Callback] bound to this instance. */
fun removeCallback(callback: Callback) {
callbacks.remove(callback)
}
@@ -149,17 +159,14 @@ class PlaybackStateManager private constructor() {
mParent = null
mQueue = musicStore.songs.toMutableList()
}
-
PlaybackMode.IN_GENRE -> {
mParent = song.genre
mQueue = song.genre.songs.toMutableList()
}
-
PlaybackMode.IN_ARTIST -> {
mParent = song.album.artist
mQueue = song.album.artist.songs.toMutableList()
}
-
PlaybackMode.IN_ALBUM -> {
mParent = song.album
mQueue = song.album.songs.toMutableList()
@@ -188,12 +195,10 @@ class PlaybackStateManager private constructor() {
mQueue = parent.songs.toMutableList()
mPlaybackMode = PlaybackMode.IN_ALBUM
}
-
is Artist -> {
mQueue = parent.songs.toMutableList()
mPlaybackMode = PlaybackMode.IN_ARTIST
}
-
is Genre -> {
mQueue = parent.songs.toMutableList()
mPlaybackMode = PlaybackMode.IN_GENRE
@@ -204,9 +209,7 @@ class PlaybackStateManager private constructor() {
updatePlayback(mQueue[0])
}
- /**
- * Shuffle all songs.
- */
+ /** Shuffle all songs. */
fun shuffleAll() {
val musicStore = MusicStore.maybeGetInstance() ?: return
@@ -218,9 +221,7 @@ class PlaybackStateManager private constructor() {
updatePlayback(mQueue[0])
}
- /**
- * Update the playback to a new [song], doing all the required logic.
- */
+ /** Update the playback to a new [song], doing all the required logic. */
private fun updatePlayback(song: Song, shouldPlay: Boolean = true) {
mSong = song
mPosition = 0
@@ -229,9 +230,7 @@ class PlaybackStateManager private constructor() {
// --- QUEUE FUNCTIONS ---
- /**
- * Go to the next song, along with doing all the checks that entails.
- */
+ /** Go to the next song, along with doing all the checks that entails. */
fun next() {
// Increment the index, if it cannot be incremented any further, then
// loop and pause/resume playback depending on the setting
@@ -246,9 +245,7 @@ class PlaybackStateManager private constructor() {
pushQueueUpdate()
}
- /**
- * Go to the previous song, doing any checks that are needed.
- */
+ /** Go to the previous song, doing any checks that are needed. */
fun prev() {
// If enabled, rewind before skipping back if the position is past 3 seconds [3000ms]
if (settingsManager.rewindWithPrev && mPosition >= REWIND_THRESHOLD) {
@@ -266,9 +263,7 @@ class PlaybackStateManager private constructor() {
// --- QUEUE EDITING FUNCTIONS ---
- /**
- * Remove a queue item at [index]. Will ignore invalid indexes.
- */
+ /** Remove a queue item at [index]. Will ignore invalid indexes. */
fun removeQueueItem(index: Int): Boolean {
if (index > mQueue.size || index < 0) {
logE("Index is out of bounds, did not remove queue item")
@@ -281,9 +276,7 @@ class PlaybackStateManager private constructor() {
return true
}
- /**
- * Move a queue item at [from] to a position at [to]. Will ignore invalid indexes.
- */
+ /** Move a queue item at [from] to a position at [to]. Will ignore invalid indexes. */
fun moveQueueItems(from: Int, to: Int): Boolean {
if (from > mQueue.size || from < 0 || to > mQueue.size || to < 0) {
logE("Indices were out of bounds, did not move queue item")
@@ -296,9 +289,7 @@ class PlaybackStateManager private constructor() {
return true
}
- /**
- * Add a [song] to the top of the queue.
- */
+ /** Add a [song] to the top of the queue. */
fun playNext(song: Song) {
if (mQueue.isEmpty()) {
return
@@ -308,9 +299,7 @@ class PlaybackStateManager private constructor() {
pushQueueUpdate()
}
- /**
- * Add a list of [songs] to the top of the queue.
- */
+ /** Add a list of [songs] to the top of the queue. */
fun playNext(songs: List) {
if (mQueue.isEmpty()) {
return
@@ -320,29 +309,21 @@ class PlaybackStateManager private constructor() {
pushQueueUpdate()
}
- /**
- * Add a [song] to the end of the queue.
- */
+ /** Add a [song] to the end of the queue. */
fun addToQueue(song: Song) {
mQueue.add(song)
pushQueueUpdate()
}
- /**
- * Add a list of [songs] to the end of the queue.
- */
+ /** Add a list of [songs] to the end of the queue. */
fun addToQueue(songs: List) {
mQueue.addAll(songs)
pushQueueUpdate()
}
- /**
- * Force any callbacks to receive a queue update.
- */
+ /** Force any callbacks to receive a queue update. */
private fun pushQueueUpdate() {
- callbacks.forEach {
- it.onQueueUpdate(mQueue, mIndex)
- }
+ callbacks.forEach { it.onQueueUpdate(mQueue, mIndex) }
}
// --- SHUFFLE FUNCTIONS ---
@@ -392,16 +373,17 @@ class PlaybackStateManager private constructor() {
val musicStore = MusicStore.maybeGetInstance() ?: return
val lastSong = mSong
- mQueue = when (mPlaybackMode) {
- PlaybackMode.ALL_SONGS ->
- settingsManager.libSongSort.sortSongs(musicStore.songs).toMutableList()
- PlaybackMode.IN_ALBUM ->
- settingsManager.detailAlbumSort.sortAlbum(mParent as Album).toMutableList()
- PlaybackMode.IN_ARTIST ->
- settingsManager.detailArtistSort.sortArtist(mParent as Artist).toMutableList()
- PlaybackMode.IN_GENRE ->
- settingsManager.detailGenreSort.sortGenre(mParent as Genre).toMutableList()
- }
+ mQueue =
+ when (mPlaybackMode) {
+ PlaybackMode.ALL_SONGS ->
+ settingsManager.libSongSort.sortSongs(musicStore.songs).toMutableList()
+ PlaybackMode.IN_ALBUM ->
+ settingsManager.detailAlbumSort.sortAlbum(mParent as Album).toMutableList()
+ PlaybackMode.IN_ARTIST ->
+ settingsManager.detailArtistSort.sortArtist(mParent as Artist).toMutableList()
+ PlaybackMode.IN_GENRE ->
+ settingsManager.detailGenreSort.sortGenre(mParent as Genre).toMutableList()
+ }
if (keepSong) {
mIndex = mQueue.indexOf(lastSong)
@@ -412,9 +394,7 @@ class PlaybackStateManager private constructor() {
// --- STATE FUNCTIONS ---
- /**
- * Set whether this instance is currently [playing].
- */
+ /** Set whether this instance is currently [playing]. */
fun setPlaying(playing: Boolean) {
if (mIsPlaying != playing) {
if (playing) {
@@ -449,39 +429,29 @@ class PlaybackStateManager private constructor() {
callbacks.forEach { it.onSeek(position) }
}
- /**
- * Rewind to the beginning of a song.
- */
+ /** Rewind to the beginning of a song. */
fun rewind() {
seekTo(0)
setPlaying(true)
}
- /**
- * Loop playback around to the beginning.
- */
+ /** Loop playback around to the beginning. */
fun loop() {
seekTo(0)
setPlaying(!settingsManager.pauseOnLoop)
}
- /**
- * Set the [LoopMode] to [mode].
- */
+ /** Set the [LoopMode] to [mode]. */
fun setLoopMode(mode: LoopMode) {
mLoopMode = mode
}
- /**
- * Mark whether this instance has played or not
- */
+ /** Mark whether this instance has played or not */
fun setHasPlayed(hasPlayed: Boolean) {
mHasPlayed = hasPlayed
}
- /**
- * Mark this instance as restored.
- */
+ /** Mark this instance as restored. */
fun markRestored() {
mIsRestored = true
}
@@ -503,16 +473,19 @@ class PlaybackStateManager private constructor() {
database.writeState(
PlaybackStateDatabase.SavedState(
- mSong, mPosition, mParent, mIndex,
- mPlaybackMode, mIsShuffling, mLoopMode,
- )
- )
+ mSong,
+ mPosition,
+ mParent,
+ mIndex,
+ mPlaybackMode,
+ mIsShuffling,
+ mLoopMode,
+ ))
database.writeQueue(mQueue)
this@PlaybackStateManager.logD(
- "State save completed successfully in ${System.currentTimeMillis() - start}ms"
- )
+ "State save completed successfully in ${System.currentTimeMillis() - start}ms")
}
}
@@ -549,9 +522,7 @@ class PlaybackStateManager private constructor() {
markRestored()
}
- /**
- * Unpack a [playbackState] into this instance.
- */
+ /** Unpack a [playbackState] into this instance. */
private fun unpackFromPlaybackState(playbackState: PlaybackStateDatabase.SavedState) {
// Turn the simplified information from PlaybackState into usable data.
@@ -573,26 +544,23 @@ class PlaybackStateManager private constructor() {
pushQueueUpdate()
}
- /**
- * Do a sanity check to make sure the parent was not lost in the restore process.
- */
+ /** Do a sanity check to make sure the parent was not lost in the restore process. */
private fun doParentSanityCheck() {
// Check if the parent was lost while in the DB.
if (mSong != null && mParent == null && mPlaybackMode != PlaybackMode.ALL_SONGS) {
logD("Parent lost, attempting restore")
- mParent = when (mPlaybackMode) {
- PlaybackMode.IN_ALBUM -> mQueue.firstOrNull()?.album
- PlaybackMode.IN_ARTIST -> mQueue.firstOrNull()?.album?.artist
- PlaybackMode.IN_GENRE -> mQueue.firstOrNull()?.genre
- PlaybackMode.ALL_SONGS -> null
- }
+ mParent =
+ when (mPlaybackMode) {
+ PlaybackMode.IN_ALBUM -> mQueue.firstOrNull()?.album
+ PlaybackMode.IN_ARTIST -> mQueue.firstOrNull()?.album?.artist
+ PlaybackMode.IN_GENRE -> mQueue.firstOrNull()?.genre
+ PlaybackMode.ALL_SONGS -> null
+ }
}
}
- /**
- * Do a sanity check to make sure that the index lines up with the current song.
- */
+ /** Do a sanity check to make sure that the index lines up with the current song. */
private fun doIndexSanityCheck() {
// Be careful with how we handle the queue since a possible index de-sync
// could easily result in an OOB crash.
@@ -639,9 +607,8 @@ class PlaybackStateManager private constructor() {
}
/**
- * The interface for receiving updates from [PlaybackStateManager].
- * Add the callback to [PlaybackStateManager] using [addCallback],
- * remove them on destruction with [removeCallback].
+ * The interface for receiving updates from [PlaybackStateManager]. Add the callback to
+ * [PlaybackStateManager] using [addCallback], remove them on destruction with [removeCallback].
*/
interface Callback {
fun onSongUpdate(song: Song?) {}
@@ -658,12 +625,9 @@ class PlaybackStateManager private constructor() {
companion object {
private const val REWIND_THRESHOLD = 3000L
- @Volatile
- private var INSTANCE: PlaybackStateManager? = null
+ @Volatile private var INSTANCE: PlaybackStateManager? = null
- /**
- * Get/Instantiate the single instance of [PlaybackStateManager].
- */
+ /** Get/Instantiate the single instance of [PlaybackStateManager]. */
fun getInstance(): PlaybackStateManager {
val currentInstance = INSTANCE
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt
index 4380eaba8..f772843a6 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AudioReactor.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.system
import android.content.Context
@@ -28,22 +27,20 @@ import androidx.media.AudioManagerCompat
import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
+import kotlin.math.pow
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
-import kotlin.math.pow
/**
* Manages the current volume and playback state across ReplayGain and AudioFocus events.
* @author OxygenCobalt
*/
-class AudioReactor(
- context: Context,
- private val callback: (Float) -> Unit
-) : AudioManager.OnAudioFocusChangeListener, SettingsManager.Callback {
+class AudioReactor(context: Context, private val callback: (Float) -> Unit) :
+ AudioManager.OnAudioFocusChangeListener, SettingsManager.Callback {
private data class Gain(val track: Float, val album: Float)
private data class GainTag(val key: String, val value: Float)
@@ -51,16 +48,16 @@ class AudioReactor(
private val settingsManager = SettingsManager.getInstance()
private val audioManager = context.getSystemServiceSafe(AudioManager::class)
- private val request = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
- .setWillPauseWhenDucked(false)
- .setAudioAttributes(
- AudioAttributesCompat.Builder()
- .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
- .setUsage(AudioAttributesCompat.USAGE_MEDIA)
- .build()
- )
- .setOnAudioFocusChangeListener(this)
- .build()
+ private val request =
+ AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
+ .setWillPauseWhenDucked(false)
+ .setAudioAttributes(
+ AudioAttributesCompat.Builder()
+ .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
+ .setUsage(AudioAttributesCompat.USAGE_MEDIA)
+ .build())
+ .setOnAudioFocusChangeListener(this)
+ .build()
private var pauseWasTransient = false
@@ -82,19 +79,16 @@ class AudioReactor(
settingsManager.addCallback(this)
}
- /**
- * Request the android system for audio focus
- */
+ /** Request the android system for audio focus */
fun requestFocus() {
logD("Requesting audio focus")
AudioManagerCompat.requestAudioFocus(audioManager, request)
}
/**
- * Updates the rough volume adjustment for [Metadata] with ReplayGain tags.
- * This is based off Vanilla Music's implementation.
- * TODO: Add ReplayGain pre-amp
- * TODO: Add positive ReplayGain values
+ * Updates the rough volume adjustment for [Metadata] with ReplayGain tags. This is based off
+ * Vanilla Music's implementation. TODO: Add ReplayGain pre-amp TODO: Add positive ReplayGain
+ * values
*/
fun applyReplayGain(metadata: Metadata?) {
if (metadata == null) {
@@ -104,49 +98,44 @@ class AudioReactor(
}
// ReplayGain is configurable, so determine what to do based off of the mode.
- val useAlbumGain: (Gain) -> Boolean = when (settingsManager.replayGainMode) {
- ReplayGainMode.OFF -> {
- logD("ReplayGain is off")
- volume = 1f
- return
+ val useAlbumGain: (Gain) -> Boolean =
+ when (settingsManager.replayGainMode) {
+ ReplayGainMode.OFF -> {
+ logD("ReplayGain is off")
+ volume = 1f
+ return
+ }
+
+ // User wants track gain to be preferred. Default to album gain only if there
+ // is no track gain.
+ ReplayGainMode.TRACK -> { gain -> gain.track == 0f }
+
+ // User wants album gain to be preferred. Default to track gain only if there
+ // is no album gain.
+ ReplayGainMode.ALBUM -> { gain -> gain.album != 0f }
+
+ // User wants album gain to be used when in an album, track gain otherwise.
+ ReplayGainMode.DYNAMIC -> { _ ->
+ playbackManager.parent is Album &&
+ playbackManager.song?.album == playbackManager.parent
+ }
}
- // User wants track gain to be preferred. Default to album gain only if there
- // is no track gain.
- ReplayGainMode.TRACK ->
- { gain ->
- gain.track == 0f
- }
-
- // User wants album gain to be preferred. Default to track gain only if there
- // is no album gain.
- ReplayGainMode.ALBUM ->
- { gain ->
- gain.album != 0f
- }
-
- // User wants album gain to be used when in an album, track gain otherwise.
- ReplayGainMode.DYNAMIC ->
- { _ ->
- playbackManager.parent is Album &&
- playbackManager.song?.album == playbackManager.parent
- }
- }
-
val gain = parseReplayGain(metadata)
- val adjust = if (gain != null) {
- if (useAlbumGain(gain)) {
- logD("Using album gain")
- gain.album
+ val adjust =
+ if (gain != null) {
+ if (useAlbumGain(gain)) {
+ logD("Using album gain")
+ gain.album
+ } else {
+ logD("Using track gain")
+ gain.track
+ }
} else {
- logD("Using track gain")
- gain.track
+ // No gain tags were present
+ 0f
}
- } else {
- // No gain tags were present
- 0f
- }
// Final adjustment along the volume curve.
// Ensure this is clamped to 0 or 1 so that it can be used as a volume.
@@ -171,12 +160,10 @@ class AudioReactor(
key = entry.description?.uppercase()
value = entry.value
}
-
is VorbisComment -> {
key = entry.key
value = entry.value
}
-
else -> continue
}
@@ -226,9 +213,7 @@ class AudioReactor(
}
}
- /**
- * Abandon the current focus request and any callbacks
- */
+ /** Abandon the current focus request and any callbacks */
fun release() {
AudioManagerCompat.abandonAudioFocusRequest(audioManager, request)
settingsManager.removeCallback(this)
@@ -302,11 +287,6 @@ class AudioReactor(
const val R128_TRACK = "R128_TRACK_GAIN"
const val R128_ALBUM = "R128_ALBUM_GAIN"
- val REPLAY_GAIN_TAGS = arrayOf(
- RG_TRACK,
- RG_ALBUM,
- R128_ALBUM,
- R128_TRACK
- )
+ val REPLAY_GAIN_TAGS = arrayOf(RG_TRACK, RG_ALBUM, R128_ALBUM, R128_TRACK)
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt
index 8cde108b2..4d16ae095 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.playback.system
import android.content.BroadcastReceiver
@@ -8,14 +25,14 @@ import androidx.core.content.ContextCompat
import org.oxycblt.auxio.util.logD
/**
- * Some apps like to party like it's 2011 and just blindly query for the ACTION_MEDIA_BUTTON
- * intent to determine the media apps on a system. *Auxio does not expose this.* Auxio exposes
- * a MediaSession that an app should control instead through the much better MediaController API.
- * But who cares about that, we need to make sure the 3% of barely functioning TouchWiz devices
- * running KitKat don't break! To prevent Auxio from not showing up at all in these apps, we
- * declare a BroadcastReceiver that deliberately handles this event. This also means that Auxio
- * will start without warning if you use the media buttons while the app exists, because I guess
- * we just have to deal with this.
+ * Some apps like to party like it's 2011 and just blindly query for the ACTION_MEDIA_BUTTON intent
+ * to determine the media apps on a system. *Auxio does not expose this.* Auxio exposes a
+ * MediaSession that an app should control instead through the much better MediaController API. But
+ * who cares about that, we need to make sure the 3% of barely functioning TouchWiz devices running
+ * KitKat don't break! To prevent Auxio from not showing up at all in these apps, we declare a
+ * BroadcastReceiver that deliberately handles this event. This also means that Auxio will start
+ * without warning if you use the media buttons while the app exists, because I guess we just have
+ * to deal with this.
* @author OxygenCobalt
*/
class MediaButtonReceiver : BroadcastReceiver() {
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt
index d0fca446f..a88e4fe99 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackNotification.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.system
import android.annotation.SuppressLint
@@ -37,15 +36,14 @@ import org.oxycblt.auxio.util.newBroadcastIntent
import org.oxycblt.auxio.util.newMainIntent
/**
- * The unified notification for [PlaybackService]. This is not self-sufficient, updates have
- * to be delivered manually.
+ * The unified notification for [PlaybackService]. This is not self-sufficient, updates have to be
+ * delivered manually.
* @author OxygenCobalt
*/
@SuppressLint("RestrictedApi")
-class PlaybackNotification private constructor(
- private val context: Context,
- mediaToken: MediaSessionCompat.Token
-) : NotificationCompat.Builder(context, CHANNEL_ID) {
+class PlaybackNotification
+private constructor(private val context: Context, mediaToken: MediaSessionCompat.Token) :
+ NotificationCompat.Builder(context, CHANNEL_ID) {
init {
setSmallIcon(R.drawable.ic_auxio)
setCategory(NotificationCompat.CATEGORY_SERVICE)
@@ -61,11 +59,7 @@ class PlaybackNotification private constructor(
addAction(buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next))
addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_exit))
- setStyle(
- MediaStyle()
- .setMediaSession(mediaToken)
- .setShowActionsInCompactView(1, 2, 3)
- )
+ setStyle(MediaStyle().setMediaSession(mediaToken).setShowActionsInCompactView(1, 2, 3))
// Don't connect to PlaybackStateManager here. This is because it's possible for this
// notification to not be updated by PlaybackStateManager before PlaybackService pushes
@@ -96,30 +90,22 @@ class PlaybackNotification private constructor(
}
}
- /**
- * Set the playing icon on the notification
- */
+ /** Set the playing icon on the notification */
fun setPlaying(isPlaying: Boolean) {
mActions[2] = buildPlayPauseAction(context, isPlaying)
}
- /**
- * Update the first action to reflect the [loopMode] given.
- */
+ /** Update the first action to reflect the [loopMode] given. */
fun setLoop(loopMode: LoopMode) {
mActions[0] = buildLoopAction(context, loopMode)
}
- /**
- * Update the first action to reflect whether the queue is shuffled or not
- */
+ /** Update the first action to reflect whether the queue is shuffled or not */
fun setShuffle(isShuffling: Boolean) {
mActions[0] = buildShuffleAction(context, isShuffling)
}
- /**
- * Apply the current [parent] to the header of the notification.
- */
+ /** Apply the current [parent] to the header of the notification. */
fun setParent(parent: MusicParent?) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
@@ -138,15 +124,13 @@ class PlaybackNotification private constructor(
return buildAction(context, PlaybackService.ACTION_PLAY_PAUSE, drawableRes)
}
- private fun buildLoopAction(
- context: Context,
- loopMode: LoopMode
- ): NotificationCompat.Action {
- val drawableRes = when (loopMode) {
- LoopMode.NONE -> R.drawable.ic_remote_loop_off
- LoopMode.ALL -> R.drawable.ic_loop
- LoopMode.TRACK -> R.drawable.ic_loop_one
- }
+ private fun buildLoopAction(context: Context, loopMode: LoopMode): NotificationCompat.Action {
+ val drawableRes =
+ when (loopMode) {
+ LoopMode.NONE -> R.drawable.ic_remote_loop_off
+ LoopMode.ALL -> R.drawable.ic_loop
+ LoopMode.TRACK -> R.drawable.ic_loop_one
+ }
return buildAction(context, PlaybackService.ACTION_LOOP, drawableRes)
}
@@ -155,7 +139,8 @@ class PlaybackNotification private constructor(
context: Context,
isShuffled: Boolean
): NotificationCompat.Action {
- val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_remote_shuffle_off
+ val drawableRes =
+ if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_remote_shuffle_off
return buildAction(context, PlaybackService.ACTION_SHUFFLE, drawableRes)
}
@@ -165,10 +150,9 @@ class PlaybackNotification private constructor(
actionName: String,
@DrawableRes iconRes: Int
): NotificationCompat.Action {
- val action = NotificationCompat.Action.Builder(
- iconRes, actionName,
- context.newBroadcastIntent(actionName)
- )
+ val action =
+ NotificationCompat.Action.Builder(
+ iconRes, actionName, context.newBroadcastIntent(actionName))
return action.build()
}
@@ -177,19 +161,18 @@ class PlaybackNotification private constructor(
const val CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel.PLAYBACK"
const val NOTIFICATION_ID = 0xA0A0
- /**
- * Build a new instance of [PlaybackNotification].
- */
+ /** Build a new instance of [PlaybackNotification]. */
fun from(
context: Context,
notificationManager: NotificationManager,
mediaSession: MediaSessionCompat
): PlaybackNotification {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val channel = NotificationChannel(
- CHANNEL_ID, context.getString(R.string.info_channel_name),
- NotificationManager.IMPORTANCE_DEFAULT
- )
+ val channel =
+ NotificationChannel(
+ CHANNEL_ID,
+ context.getString(R.string.info_channel_name),
+ NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
index c8e0d8ccc..485859d23 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackService.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.system
import android.app.NotificationManager
@@ -69,11 +68,12 @@ import org.oxycblt.auxio.widgets.WidgetProvider
* - Headset management
* - Widgets
*
- * This service relies on [PlaybackStateManager.Callback] and [SettingsManager.Callback],
- * so therefore there's no need to bind to it to deliver commands.
+ * This service relies on [PlaybackStateManager.Callback] and [SettingsManager.Callback], so
+ * therefore there's no need to bind to it to deliver commands.
* @author OxygenCobalt
*/
-class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
+class PlaybackService :
+ Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
// Player components
private lateinit var player: ExoPlayer
private lateinit var mediaSession: MediaSessionCompat
@@ -126,22 +126,20 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
.setUsage(C.USAGE_MEDIA)
.setContentType(C.CONTENT_TYPE_MUSIC)
.build(),
- false
- )
+ false)
- audioReactor = AudioReactor(this) { volume ->
- logD("Updating player volume to $volume")
- player.volume = volume
- }
+ audioReactor =
+ AudioReactor(this) { volume ->
+ logD("Updating player volume to $volume")
+ player.volume = volume
+ }
// --- SYSTEM SETUP ---
widgets = WidgetController(this)
// Set up the media button callbacks
- mediaSession = MediaSessionCompat(this, packageName).apply {
- isActive = true
- }
+ mediaSession = MediaSessionCompat(this, packageName).apply { isActive = true }
connector = PlaybackSessionConnector(this, player, mediaSession)
@@ -215,7 +213,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
override fun onPlaybackStateChanged(state: Int) {
when (state) {
Player.STATE_READY -> startPolling()
-
Player.STATE_ENDED -> {
if (playbackManager.loopMode == LoopMode.TRACK) {
playbackManager.loop()
@@ -223,7 +220,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
playbackManager.next()
}
}
-
else -> {}
}
}
@@ -347,17 +343,14 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
// --- OTHER FUNCTIONS ---
- /**
- * Create the [ExoPlayer] instance.
- */
+ /** Create the [ExoPlayer] instance. */
private fun newPlayer(): ExoPlayer {
// Since Auxio is a music player, only specify an audio renderer to save
// battery/apk size/cache size
val audioRenderer = RenderersFactory { handler, _, audioListener, _, _ ->
arrayOf(
MediaCodecAudioRenderer(this, MediaCodecSelector.DEFAULT, handler, audioListener),
- LibflacAudioRenderer(handler, audioListener)
- )
+ LibflacAudioRenderer(handler, audioListener))
}
// Enable constant bitrate seeking so that certain MP3s/AACs are seekable
@@ -369,9 +362,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
.build()
}
- /**
- * Fully restore the notification and playback state
- */
+ /** Fully restore the notification and playback state */
private fun restore() {
logD("Restoring the service state")
@@ -387,16 +378,16 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
widgets.update()
}
- /**
- * Start polling the position on a coroutine.
- */
+ /** Start polling the position on a coroutine. */
private fun startPolling() {
- val pollFlow = flow {
- while (true) {
- emit(player.currentPosition)
- delay(POS_POLL_INTERVAL)
- }
- }.conflate()
+ val pollFlow =
+ flow {
+ while (true) {
+ emit(player.currentPosition)
+ delay(POS_POLL_INTERVAL)
+ }
+ }
+ .conflate()
serviceScope.launch {
pollFlow.takeWhile { player.isPlaying }.collect { pos ->
@@ -416,9 +407,9 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
// Specify that this is a media service, if supported.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
- PlaybackNotification.NOTIFICATION_ID, notification.build(),
- ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
- )
+ PlaybackNotification.NOTIFICATION_ID,
+ notification.build(),
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
} else {
startForeground(PlaybackNotification.NOTIFICATION_ID, notification.build())
}
@@ -427,24 +418,19 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
} else {
// If we are already in foreground just update the notification
notificationManager.notify(
- PlaybackNotification.NOTIFICATION_ID, notification.build()
- )
+ PlaybackNotification.NOTIFICATION_ID, notification.build())
}
}
}
- /**
- * Stop the foreground state and hide the notification
- */
+ /** Stop the foreground state and hide the notification */
private fun stopForegroundAndNotification() {
stopForeground(true)
notificationManager.cancel(PlaybackNotification.NOTIFICATION_ID)
isForeground = false
}
- /**
- * A [BroadcastReceiver] for receiving general playback events from the system.
- */
+ /** A [BroadcastReceiver] for receiving general playback events from the system. */
private inner class PlaybackReceiver : BroadcastReceiver() {
private var initialHeadsetPlugEventHandled = false
@@ -477,56 +463,44 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
AudioManager.ACTION_AUDIO_BECOMING_NOISY -> pauseFromPlug()
// --- AUXIO EVENTS ---
- ACTION_PLAY_PAUSE -> playbackManager.setPlaying(
- !playbackManager.isPlaying
- )
-
- ACTION_LOOP -> playbackManager.setLoopMode(
- playbackManager.loopMode.increment()
- )
-
- ACTION_SHUFFLE -> playbackManager.setShuffling(
- !playbackManager.isShuffling, keepSong = true
- )
-
+ ACTION_PLAY_PAUSE -> playbackManager.setPlaying(!playbackManager.isPlaying)
+ ACTION_LOOP -> playbackManager.setLoopMode(playbackManager.loopMode.increment())
+ ACTION_SHUFFLE ->
+ playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true)
ACTION_SKIP_PREV -> playbackManager.prev()
ACTION_SKIP_NEXT -> playbackManager.next()
-
ACTION_EXIT -> {
playbackManager.setPlaying(false)
stopForegroundAndNotification()
}
-
WidgetProvider.ACTION_WIDGET_UPDATE -> widgets.update()
}
}
/**
- * Resume from a headset plug event in the case that the quirk is enabled.
- * This functionality remains a quirk for two reasons:
- * 1. Automatically resuming more or less overrides all other audio streams, which
- * is not that friendly
- * 2. There is a bug where playback will always start when this service starts, mostly
- * due to AudioManager.ACTION_HEADSET_PLUG always firing on startup. This is fixed, but
- * I fear that it may not work on OEM skins that for whatever reason don't make this
- * action fire.
- * TODO: Figure out how players like Retro are able to get autoplay working with
- * bluetooth headsets
+ * Resume from a headset plug event in the case that the quirk is enabled. This
+ * functionality remains a quirk for two reasons:
+ * 1. Automatically resuming more or less overrides all other audio streams, which is not
+ * that friendly
+ * 2. There is a bug where playback will always start when this service starts, mostly due
+ * to AudioManager.ACTION_HEADSET_PLUG always firing on startup. This is fixed, but I fear
+ * that it may not work on OEM skins that for whatever reason don't make this action fire.
+ * TODO: Figure out how players like Retro are able to get autoplay working with bluetooth
+ * headsets
*/
private fun maybeResumeFromPlug() {
if (playbackManager.song != null &&
settingsManager.headsetAutoplay &&
- initialHeadsetPlugEventHandled
- ) {
+ initialHeadsetPlugEventHandled) {
logD("Device connected, resuming")
playbackManager.setPlaying(true)
}
}
/**
- * Pause from a headset plug.
- * TODO: Find a way to centralize this stuff into a single BroadcastReciever instead
- * of the weird disjointed arrangement between MediaSession and this.
+ * Pause from a headset plug. TODO: Find a way to centralize this stuff into a single
+ * BroadcastReciever instead of the weird disjointed arrangement between MediaSession and
+ * this.
*/
private fun pauseFromPlug() {
if (playbackManager.song != null) {
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt
index 0c211c683..afb9c999c 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackSessionConnector.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.playback.system
import android.content.Context
@@ -32,8 +31,8 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.util.logD
/**
- * Nightmarish class that coordinates communication between [MediaSessionCompat], [Player],
- * and [PlaybackStateManager].
+ * Nightmarish class that coordinates communication between [MediaSessionCompat], [Player], and
+ * [PlaybackStateManager].
*/
class PlaybackSessionConnector(
private val context: Context,
@@ -84,12 +83,13 @@ class PlaybackSessionConnector(
}
override fun onSetRepeatMode(repeatMode: Int) {
- val mode = when (repeatMode) {
- PlaybackStateCompat.REPEAT_MODE_ALL -> LoopMode.ALL
- PlaybackStateCompat.REPEAT_MODE_GROUP -> LoopMode.ALL
- PlaybackStateCompat.REPEAT_MODE_ONE -> LoopMode.TRACK
- else -> LoopMode.NONE
- }
+ val mode =
+ when (repeatMode) {
+ PlaybackStateCompat.REPEAT_MODE_ALL -> LoopMode.ALL
+ PlaybackStateCompat.REPEAT_MODE_GROUP -> LoopMode.ALL
+ PlaybackStateCompat.REPEAT_MODE_ONE -> LoopMode.TRACK
+ else -> LoopMode.NONE
+ }
playbackManager.setLoopMode(mode)
}
@@ -98,8 +98,7 @@ class PlaybackSessionConnector(
playbackManager.setShuffling(
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL ||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP,
- true
- )
+ true)
}
override fun onStop() {
@@ -117,15 +116,16 @@ class PlaybackSessionConnector(
val artistName = song.resolvedArtistName
- val builder = MediaMetadataCompat.Builder()
- .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.name)
- .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, song.name)
- .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artistName)
- .putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, artistName)
- .putString(MediaMetadataCompat.METADATA_KEY_COMPOSER, artistName)
- .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, artistName)
- .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.resolvedAlbumName)
- .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
+ val builder =
+ MediaMetadataCompat.Builder()
+ .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.name)
+ .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, song.name)
+ .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artistName)
+ .putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, artistName)
+ .putString(MediaMetadataCompat.METADATA_KEY_COMPOSER, artistName)
+ .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, artistName)
+ .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.resolvedAlbumName)
+ .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration)
// Load the cover asynchronously. This is the entire reason I don't use a plain
// MediaSessionConnector, which AFAIK makes it impossible to load this the way I do
@@ -144,14 +144,12 @@ class PlaybackSessionConnector(
override fun onEvents(player: Player, events: Player.Events) {
if (events.containsAny(
- Player.EVENT_POSITION_DISCONTINUITY,
- Player.EVENT_PLAYBACK_STATE_CHANGED,
- Player.EVENT_PLAY_WHEN_READY_CHANGED,
- Player.EVENT_IS_PLAYING_CHANGED,
- Player.EVENT_REPEAT_MODE_CHANGED,
- Player.EVENT_PLAYBACK_PARAMETERS_CHANGED
- )
- ) {
+ Player.EVENT_POSITION_DISCONTINUITY,
+ Player.EVENT_PLAYBACK_STATE_CHANGED,
+ Player.EVENT_PLAY_WHEN_READY_CHANGED,
+ Player.EVENT_IS_PLAYING_CHANGED,
+ Player.EVENT_REPEAT_MODE_CHANGED,
+ Player.EVENT_PLAYBACK_PARAMETERS_CHANGED)) {
invalidateSessionState()
}
}
@@ -162,24 +160,20 @@ class PlaybackSessionConnector(
logD("Updating media session state")
// Position updates arrive faster when you upload STATE_PAUSED for some insane reason.
- val state = PlaybackStateCompat.Builder()
- .setActions(ACTIONS)
- .setBufferedPosition(player.bufferedPosition)
- .setState(
- PlaybackStateCompat.STATE_PAUSED,
- player.currentPosition,
- 1.0f,
- SystemClock.elapsedRealtime()
- )
+ val state =
+ PlaybackStateCompat.Builder()
+ .setActions(ACTIONS)
+ .setBufferedPosition(player.bufferedPosition)
+ .setState(
+ PlaybackStateCompat.STATE_PAUSED,
+ player.currentPosition,
+ 1.0f,
+ SystemClock.elapsedRealtime())
mediaSession.setPlaybackState(state.build())
state.setState(
- getPlayerState(),
- player.currentPosition,
- 1.0f,
- SystemClock.elapsedRealtime()
- )
+ getPlayerState(), player.currentPosition, 1.0f, SystemClock.elapsedRealtime())
mediaSession.setPlaybackState(state.build())
}
@@ -199,14 +193,15 @@ class PlaybackSessionConnector(
}
companion object {
- const val ACTIONS = PlaybackStateCompat.ACTION_PLAY or
- PlaybackStateCompat.ACTION_PAUSE or
- PlaybackStateCompat.ACTION_PLAY_PAUSE or
- PlaybackStateCompat.ACTION_SET_REPEAT_MODE or
- PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE or
- PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
- PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
- PlaybackStateCompat.ACTION_SEEK_TO or
- PlaybackStateCompat.ACTION_STOP
+ const val ACTIONS =
+ PlaybackStateCompat.ACTION_PLAY or
+ PlaybackStateCompat.ACTION_PAUSE or
+ PlaybackStateCompat.ACTION_PLAY_PAUSE or
+ PlaybackStateCompat.ACTION_SET_REPEAT_MODE or
+ PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE or
+ PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
+ PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
+ PlaybackStateCompat.ACTION_SEEK_TO or
+ PlaybackStateCompat.ACTION_STOP
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt
index 023671efc..fba56c0f9 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/system/ReplayGainMode.kt
@@ -1,8 +1,23 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.playback.system
-/**
- * Represents the current setting for ReplayGain.
- */
+/** Represents the current setting for ReplayGain. */
enum class ReplayGainMode {
/** Do not apply ReplayGain. */
OFF,
@@ -13,9 +28,7 @@ enum class ReplayGainMode {
/** Apply the album gain only when playing from an album, defaulting to track gain otherwise. */
DYNAMIC;
- /**
- * Converts this type to an integer constant.
- */
+ /** Converts this type to an integer constant. */
fun toInt(): Int {
return when (this) {
OFF -> INT_OFF
@@ -31,9 +44,7 @@ enum class ReplayGainMode {
private const val INT_ALBUM = 0xA112
private const val INT_DYNAMIC = 0xA113
- /**
- * Converts an integer constant to this type.
- */
+ /** Converts an integer constant to this type. */
fun fromInt(value: Int): ReplayGainMode? {
return when (value) {
INT_OFF -> OFF
diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt
index 3a7059520..4b3d6f7ae 100644
--- a/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt
+++ b/app/src/main/java/org/oxycblt/auxio/search/SearchAdapter.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SearchAdapter.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.search
import android.view.View
@@ -58,24 +57,15 @@ class SearchAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
- GenreViewHolder.ITEM_TYPE -> GenreViewHolder.from(
- parent.context, doOnClick, doOnLongClick
- )
-
- ArtistViewHolder.ITEM_TYPE -> ArtistViewHolder.from(
- parent.context, doOnClick, doOnLongClick
- )
-
- AlbumViewHolder.ITEM_TYPE -> AlbumViewHolder.from(
- parent.context, doOnClick, doOnLongClick
- )
-
- SongViewHolder.ITEM_TYPE -> SongViewHolder.from(
- parent.context, doOnClick, doOnLongClick
- )
-
+ GenreViewHolder.ITEM_TYPE ->
+ GenreViewHolder.from(parent.context, doOnClick, doOnLongClick)
+ ArtistViewHolder.ITEM_TYPE ->
+ ArtistViewHolder.from(parent.context, doOnClick, doOnLongClick)
+ AlbumViewHolder.ITEM_TYPE ->
+ AlbumViewHolder.from(parent.context, doOnClick, doOnLongClick)
+ SongViewHolder.ITEM_TYPE ->
+ SongViewHolder.from(parent.context, doOnClick, doOnLongClick)
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
-
else -> error("Invalid ViewHolder item type")
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt
index f83e3b566..ae2ac2011 100644
--- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SearchFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.search
import android.os.Bundle
@@ -67,24 +66,21 @@ class SearchFragment : Fragment() {
val imm = requireContext().getSystemServiceSafe(InputMethodManager::class)
- val searchAdapter = SearchAdapter(
- doOnClick = { item ->
- onItemSelection(item, imm)
- },
- ::newMenu
- )
+ val searchAdapter =
+ SearchAdapter(doOnClick = { item -> onItemSelection(item, imm) }, ::newMenu)
// --- UI SETUP --
binding.lifecycleOwner = viewLifecycleOwner
binding.searchToolbar.apply {
- val itemId = when (searchModel.filterMode) {
- DisplayMode.SHOW_SONGS -> R.id.option_filter_songs
- DisplayMode.SHOW_ALBUMS -> R.id.option_filter_albums
- DisplayMode.SHOW_ARTISTS -> R.id.option_filter_artists
- DisplayMode.SHOW_GENRES -> R.id.option_filter_genres
- null -> R.id.option_filter_all
- }
+ val itemId =
+ when (searchModel.filterMode) {
+ DisplayMode.SHOW_SONGS -> R.id.option_filter_songs
+ DisplayMode.SHOW_ALBUMS -> R.id.option_filter_albums
+ DisplayMode.SHOW_ARTISTS -> R.id.option_filter_artists
+ DisplayMode.SHOW_GENRES -> R.id.option_filter_genres
+ null -> R.id.option_filter_all
+ }
menu.findItem(itemId).isChecked = true
@@ -114,9 +110,7 @@ class SearchFragment : Fragment() {
if (!launchedKeyboard) {
// Auto-open the keyboard when this view is shown
requestFocus()
- postDelayed(200) {
- imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
- }
+ postDelayed(200) { imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) }
launchedKeyboard = true
}
@@ -125,9 +119,7 @@ class SearchFragment : Fragment() {
binding.searchRecycler.apply {
adapter = searchAdapter
- applySpans { pos ->
- searchAdapter.currentList[pos] is Header
- }
+ applySpans { pos -> searchAdapter.currentList[pos] is Header }
}
// --- VIEWMODEL SETUP ---
@@ -148,15 +140,14 @@ class SearchFragment : Fragment() {
}
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
- findNavController().navigate(
- when (item) {
- is Song -> SearchFragmentDirections.actionShowAlbum(item.album.id)
- is Album -> SearchFragmentDirections.actionShowAlbum(item.id)
- is Artist -> SearchFragmentDirections.actionShowArtist(item.id)
-
- else -> return@observe
- }
- )
+ findNavController()
+ .navigate(
+ when (item) {
+ is Song -> SearchFragmentDirections.actionShowAlbum(item.album.id)
+ is Album -> SearchFragmentDirections.actionShowAlbum(item.id)
+ is Artist -> SearchFragmentDirections.actionShowArtist(item.id)
+ else -> return@observe
+ })
imm.hide()
}
@@ -177,8 +168,7 @@ class SearchFragment : Fragment() {
}
/**
- * Function that handles when an [item] is selected.
- * Handles all datatypes that are selectable.
+ * Function that handles when an [item] is selected. Handles all datatypes that are selectable.
*/
private fun onItemSelection(item: Music, imm: InputMethodManager) {
if (item is Song) {
@@ -192,20 +182,20 @@ class SearchFragment : Fragment() {
logD("Navigating to the detail fragment for ${item.name}")
- findNavController().navigate(
- when (item) {
- is Genre -> SearchFragmentDirections.actionShowGenre(item.id)
- is Artist -> SearchFragmentDirections.actionShowArtist(item.id)
- is Album -> SearchFragmentDirections.actionShowAlbum(item.id)
+ findNavController()
+ .navigate(
+ when (item) {
+ is Genre -> SearchFragmentDirections.actionShowGenre(item.id)
+ is Artist -> SearchFragmentDirections.actionShowArtist(item.id)
+ is Album -> SearchFragmentDirections.actionShowAlbum(item.id)
- // If given model wasn't valid, then reset the navigation status
- // and abort the navigation.
- else -> {
- searchModel.setNavigating(false)
- return
- }
- }
- )
+ // If given model wasn't valid, then reset the navigation status
+ // and abort the navigation.
+ else -> {
+ searchModel.setNavigating(false)
+ return
+ }
+ })
imm.hide()
}
diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt
index 2f5445651..2cccf928e 100644
--- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SearchViewModel.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.search
import androidx.annotation.IdRes
@@ -23,6 +22,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import java.text.Normalizer
import kotlinx.coroutines.launch
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Header
@@ -34,7 +34,6 @@ import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.logD
-import java.text.Normalizer
/**
* The [ViewModel] for the search functionality
@@ -47,9 +46,12 @@ class SearchViewModel : ViewModel() {
private var mLastQuery = ""
/** Current search results from the last [search] call. */
- val searchResults: LiveData
> get() = mSearchResults
- val isNavigating: Boolean get() = mIsNavigating
- val filterMode: DisplayMode? get() = mFilterMode
+ val searchResults: LiveData>
+ get() = mSearchResults
+ val isNavigating: Boolean
+ get() = mIsNavigating
+ val filterMode: DisplayMode?
+ get() = mFilterMode
private val settingsManager = SettingsManager.getInstance()
@@ -63,8 +65,7 @@ class SearchViewModel : ViewModel() {
}
/**
- * Use [query] to perform a search of the music library.
- * Will push results to [searchResults].
+ * Use [query] to perform a search of the music library. Will push results to [searchResults].
*/
fun search(query: String) {
val musicStore = MusicStore.maybeGetInstance()
@@ -118,18 +119,17 @@ class SearchViewModel : ViewModel() {
}
/**
- * Update the current filter mode with a menu [id].
- * New value will be pushed to [filterMode].
+ * Update the current filter mode with a menu [id]. New value will be pushed to [filterMode].
*/
fun updateFilterModeWithId(@IdRes id: Int) {
- mFilterMode = when (id) {
- R.id.option_filter_songs -> DisplayMode.SHOW_SONGS
- R.id.option_filter_albums -> DisplayMode.SHOW_ALBUMS
- R.id.option_filter_artists -> DisplayMode.SHOW_ARTISTS
- R.id.option_filter_genres -> DisplayMode.SHOW_GENRES
-
- else -> null
- }
+ mFilterMode =
+ when (id) {
+ R.id.option_filter_songs -> DisplayMode.SHOW_SONGS
+ R.id.option_filter_albums -> DisplayMode.SHOW_ALBUMS
+ R.id.option_filter_artists -> DisplayMode.SHOW_ARTISTS
+ R.id.option_filter_genres -> DisplayMode.SHOW_GENRES
+ else -> null
+ }
logD("Updating filter mode to $mFilterMode")
@@ -139,17 +139,18 @@ class SearchViewModel : ViewModel() {
}
/**
- * Shortcut that will run a ignoreCase filter on a list and only return
- * a value if the resulting list is empty.
+ * Shortcut that will run a ignoreCase filter on a list and only return a value if the resulting
+ * list is empty.
*/
private fun List.filterByOrNull(value: String): List? {
val filtered = filter {
// Ensure the name we match with is correct.
- val name = if (it is MusicParent) {
- it.resolvedName
- } else {
- it.name
- }
+ val name =
+ if (it is MusicParent) {
+ it.resolvedName
+ } else {
+ it.name
+ }
// First see if the normal item name will work. If that fails, try the "normalized"
// [e.g all accented/unicode chars become latin chars] instead. Hopefully this
@@ -182,8 +183,8 @@ class SearchViewModel : ViewModel() {
when (Character.getType(cp)) {
// Character.NON_SPACING_MARK and Character.COMBINING_SPACING_MARK were added
// by normalizer
- 6, 8 -> continue
-
+ 6,
+ 8 -> continue
else -> sb.appendCodePoint(cp)
}
}
@@ -191,9 +192,7 @@ class SearchViewModel : ViewModel() {
return sb.toString()
}
- /**
- * Update the current navigation status to [isNavigating]
- */
+ /** Update the current navigation status to [isNavigating] */
fun setNavigating(isNavigating: Boolean) {
mIsNavigating = isNavigating
}
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt
index f24110e70..cc95de2ee 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * AboutFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.settings
import android.content.ActivityNotFoundException
@@ -59,9 +58,7 @@ class AboutFragment : Fragment() {
insets
}
- binding.aboutToolbar.setNavigationOnClickListener {
- findNavController().navigateUp()
- }
+ binding.aboutToolbar.setNavigationOnClickListener { findNavController().navigateUp() }
binding.aboutVersion.text = BuildConfig.VERSION_NAME
binding.aboutCode.setOnClickListener { openLinkInBrowser(LINK_CODEBASE) }
@@ -69,9 +66,7 @@ class AboutFragment : Fragment() {
binding.aboutLicenses.setOnClickListener { openLinkInBrowser(LINK_LICENSES) }
homeModel.songs.observe(viewLifecycleOwner) { songs ->
- binding.aboutSongCount.text = getString(
- R.string.fmt_songs_loaded, songs.size
- )
+ binding.aboutSongCount.text = getString(R.string.fmt_songs_loaded, songs.size)
}
logD("Dialog created")
@@ -79,15 +74,12 @@ class AboutFragment : Fragment() {
return binding.root
}
- /**
- * Go through the process of opening a [link] in a browser.
- */
+ /** Go through the process of opening a [link] in a browser. */
private fun openLinkInBrowser(link: String) {
logD("Opening $link")
- val browserIntent = Intent(Intent.ACTION_VIEW, link.toUri()).setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK
- )
+ val browserIntent =
+ Intent(Intent.ACTION_VIEW, link.toUri()).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11 seems to now handle the app chooser situations on its own now
@@ -104,9 +96,12 @@ class AboutFragment : Fragment() {
// 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
// browser.
- val pkgName = requireContext().packageManager.resolveActivity(
- browserIntent, PackageManager.MATCH_DEFAULT_ONLY
- )?.activityInfo?.packageName
+ val pkgName =
+ requireContext()
+ .packageManager
+ .resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
+ ?.activityInfo
+ ?.packageName
if (pkgName != null) {
if (pkgName == "android") {
@@ -130,9 +125,10 @@ class AboutFragment : Fragment() {
}
private fun openAppChooser(intent: Intent) {
- val chooserIntent = Intent(Intent.ACTION_CHOOSER)
- .putExtra(Intent.EXTRA_INTENT, intent)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ val chooserIntent =
+ Intent(Intent.ACTION_CHOOSER)
+ .putExtra(Intent.EXTRA_INTENT, intent)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(chooserIntent)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt
index 8ee87c17e..7e13e07fe 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SettingsCompat.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.settings
import android.content.SharedPreferences
@@ -53,9 +52,7 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent {
return Accent(prefs.getInt(SettingsManager.KEY_ACCENT, 5))
}
-/**
- * Cache of the old keys used in Auxio.
- */
+/** Cache of the old keys used in Auxio. */
private object OldKeys {
const val KEY_ACCENT2 = "KEY_ACCENT2"
}
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt
index 4b0ef7dfc..f6dd15587 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SettingsFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.settings
import android.os.Bundle
@@ -39,9 +38,7 @@ class SettingsFragment : Fragment() {
val binding = FragmentSettingsBinding.inflate(inflater)
binding.settingsToolbar.apply {
- setNavigationOnClickListener {
- findNavController().navigateUp()
- }
+ setNavigationOnClickListener { findNavController().navigateUp() }
}
binding.settingsAppbar.liftOnScrollTargetViewId = androidx.preference.R.id.recycler_view
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
index 3b8e2a278..4ec9768a5 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SettingsListFragment.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.settings
import android.os.Bundle
@@ -32,8 +31,8 @@ import androidx.recyclerview.widget.RecyclerView
import coil.Coil
import org.oxycblt.auxio.R
import org.oxycblt.auxio.accent.AccentCustomizeDialog
-import org.oxycblt.auxio.music.excluded.ExcludedDialog
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
+import org.oxycblt.auxio.music.excluded.ExcludedDialog
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.pref.IntListPrefDialog
import org.oxycblt.auxio.settings.pref.IntListPreference
@@ -83,9 +82,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
}
}
- /**
- * Recursively handle a preference, doing any specific actions on it.
- */
+ /** Recursively handle a preference, doing any specific actions on it. */
private fun recursivelyHandlePreference(preference: Preference) {
if (!preference.isVisible) return
@@ -100,82 +97,80 @@ class SettingsListFragment : PreferenceFragmentCompat() {
SettingsManager.KEY_THEME -> {
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
- onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, value ->
- AppCompatDelegate.setDefaultNightMode(value as Int)
- setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
- true
- }
- }
-
- SettingsManager.KEY_BLACK_THEME -> {
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- if (requireContext().isNight) {
- requireActivity().recreate()
+ onPreferenceChangeListener =
+ Preference.OnPreferenceChangeListener { _, value ->
+ AppCompatDelegate.setDefaultNightMode(value as Int)
+ setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
+ true
}
-
- true
- }
}
+ SettingsManager.KEY_BLACK_THEME -> {
+ onPreferenceClickListener =
+ Preference.OnPreferenceClickListener {
+ if (requireContext().isNight) {
+ requireActivity().recreate()
+ }
+ true
+ }
+ }
SettingsManager.KEY_ACCENT -> {
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- AccentCustomizeDialog().show(childFragmentManager, AccentCustomizeDialog.TAG)
- true
- }
+ onPreferenceClickListener =
+ Preference.OnPreferenceClickListener {
+ AccentCustomizeDialog()
+ .show(childFragmentManager, AccentCustomizeDialog.TAG)
+ true
+ }
summary = context.getString(settingsManager.accent.name)
}
-
SettingsManager.KEY_LIB_TABS -> {
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
- true
- }
+ onPreferenceClickListener =
+ Preference.OnPreferenceClickListener {
+ TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
+ true
+ }
}
-
SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS -> {
- onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
- Coil.imageLoader(requireContext()).apply {
- this.memoryCache?.clear()
+ onPreferenceChangeListener =
+ Preference.OnPreferenceChangeListener { _, _ ->
+ Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() }
+
+ true
}
-
- true
- }
}
-
SettingsManager.KEY_SAVE_STATE -> {
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- playbackModel.savePlaybackState(requireContext()) {
- requireContext().showToast(R.string.lbl_state_saved)
+ onPreferenceClickListener =
+ Preference.OnPreferenceClickListener {
+ playbackModel.savePlaybackState(requireContext()) {
+ requireContext().showToast(R.string.lbl_state_saved)
+ }
+
+ true
}
-
- true
- }
}
-
SettingsManager.KEY_RELOAD -> {
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- playbackModel.savePlaybackState(requireContext()) {
- requireContext().hardRestart()
+ onPreferenceClickListener =
+ Preference.OnPreferenceClickListener {
+ playbackModel.savePlaybackState(requireContext()) {
+ requireContext().hardRestart()
+ }
+
+ true
}
-
- true
- }
}
-
SettingsManager.KEY_EXCLUDED -> {
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG)
- true
- }
+ onPreferenceClickListener =
+ Preference.OnPreferenceClickListener {
+ ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG)
+ true
+ }
}
}
}
}
- /**
- * Convert an theme integer into an icon that can be used.
- */
+ /** Convert an theme integer into an icon that can be used. */
@DrawableRes
private fun Int.toThemeIcon(): Int {
return when (this) {
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt
index 47678c1b6..70267a1df 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SettingsManager.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.settings
import android.content.Context
@@ -64,16 +63,17 @@ class SettingsManager private constructor(context: Context) :
}
/**
- * Whether to display the LoopMode or the shuffle status on the notification.
- * False if loop, true if shuffle.
+ * Whether to display the LoopMode or the shuffle status on the notification. False if loop,
+ * true if shuffle.
*/
val useAltNotifAction: Boolean
get() = prefs.getBoolean(KEY_USE_ALT_NOTIFICATION_ACTION, false)
/** The current library tabs preferred by the user. */
var libTabs: Array
- get() = Tab.fromSequence(prefs.getInt(KEY_LIB_TABS, Tab.SEQUENCE_DEFAULT))
- ?: Tab.fromSequence(Tab.SEQUENCE_DEFAULT)!!
+ get() =
+ Tab.fromSequence(prefs.getInt(KEY_LIB_TABS, Tab.SEQUENCE_DEFAULT))
+ ?: Tab.fromSequence(Tab.SEQUENCE_DEFAULT)!!
set(value) {
prefs.edit {
putInt(KEY_LIB_TABS, Tab.toSequence(value))
@@ -103,13 +103,15 @@ class SettingsManager private constructor(context: Context) :
/** The current ReplayGain configuration */
val replayGainMode: ReplayGainMode
- get() = ReplayGainMode.fromInt(prefs.getInt(KEY_REPLAY_GAIN, Int.MIN_VALUE))
- ?: ReplayGainMode.OFF
+ get() =
+ ReplayGainMode.fromInt(prefs.getInt(KEY_REPLAY_GAIN, Int.MIN_VALUE))
+ ?: ReplayGainMode.OFF
/** What queue to create when a song is selected (ex. From All Songs or Search) */
val songPlaybackMode: PlaybackMode
- get() = PlaybackMode.fromInt(prefs.getInt(KEY_SONG_PLAYBACK_MODE, Int.MIN_VALUE))
- ?: PlaybackMode.ALL_SONGS
+ get() =
+ PlaybackMode.fromInt(prefs.getInt(KEY_SONG_PLAYBACK_MODE, Int.MIN_VALUE))
+ ?: PlaybackMode.ALL_SONGS
/** Whether shuffle should stay on when a new song is selected. */
val keepShuffle: Boolean
@@ -119,7 +121,9 @@ class SettingsManager private constructor(context: Context) :
val rewindWithPrev: Boolean
get() = prefs.getBoolean(KEY_PREV_REWIND, true)
- /** Whether [org.oxycblt.auxio.playback.state.LoopMode.TRACK] should pause when the track repeats */
+ /**
+ * Whether [org.oxycblt.auxio.playback.state.LoopMode.TRACK] should pause when the track repeats
+ */
val pauseOnLoop: Boolean
get() = prefs.getBoolean(KEY_LOOP_PAUSE, false)
@@ -133,10 +137,9 @@ class SettingsManager private constructor(context: Context) :
}
}
- /** The song sort mode on HomeFragment **/
+ /** The song sort mode on HomeFragment */
var libSongSort: Sort
- get() = Sort.fromInt(prefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE))
- ?: Sort.ByName(true)
+ get() = Sort.fromInt(prefs.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_SONGS_SORT, value.toInt())
@@ -144,10 +147,9 @@ class SettingsManager private constructor(context: Context) :
}
}
- /** The album sort mode on HomeFragment **/
+ /** The album sort mode on HomeFragment */
var libAlbumSort: Sort
- get() = Sort.fromInt(prefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE))
- ?: Sort.ByName(true)
+ get() = Sort.fromInt(prefs.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_ALBUMS_SORT, value.toInt())
@@ -155,10 +157,9 @@ class SettingsManager private constructor(context: Context) :
}
}
- /** The artist sort mode on HomeFragment **/
+ /** The artist sort mode on HomeFragment */
var libArtistSort: Sort
- get() = Sort.fromInt(prefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE))
- ?: Sort.ByName(true)
+ get() = Sort.fromInt(prefs.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_ARTISTS_SORT, value.toInt())
@@ -166,10 +167,9 @@ class SettingsManager private constructor(context: Context) :
}
}
- /** The genre sort mode on HomeFragment **/
+ /** The genre sort mode on HomeFragment */
var libGenreSort: Sort
- get() = Sort.fromInt(prefs.getInt(KEY_LIB_GENRES_SORT, Int.MIN_VALUE))
- ?: Sort.ByName(true)
+ get() = Sort.fromInt(prefs.getInt(KEY_LIB_GENRES_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_LIB_GENRES_SORT, value.toInt())
@@ -177,10 +177,10 @@ class SettingsManager private constructor(context: Context) :
}
}
- /** The detail album sort mode **/
+ /** The detail album sort mode */
var detailAlbumSort: Sort
- get() = Sort.fromInt(prefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE))
- ?: Sort.ByName(true)
+ get() =
+ Sort.fromInt(prefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_DETAIL_ALBUM_SORT, value.toInt())
@@ -188,10 +188,10 @@ class SettingsManager private constructor(context: Context) :
}
}
- /** The detail artist sort mode **/
+ /** The detail artist sort mode */
var detailArtistSort: Sort
- get() = Sort.fromInt(prefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE))
- ?: Sort.ByYear(false)
+ get() =
+ Sort.fromInt(prefs.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE)) ?: Sort.ByYear(false)
set(value) {
prefs.edit {
putInt(KEY_DETAIL_ARTIST_SORT, value.toInt())
@@ -199,10 +199,10 @@ class SettingsManager private constructor(context: Context) :
}
}
- /** The detail genre sort mode **/
+ /** The detail genre sort mode */
var detailGenreSort: Sort
- get() = Sort.fromInt(prefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE))
- ?: Sort.ByName(true)
+ get() =
+ Sort.fromInt(prefs.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
prefs.edit {
putInt(KEY_DETAIL_GENRE_SORT, value.toInt())
@@ -226,29 +226,13 @@ class SettingsManager private constructor(context: Context) :
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
- KEY_USE_ALT_NOTIFICATION_ACTION -> callbacks.forEach {
- it.onNotifActionUpdate(useAltNotifAction)
- }
-
- KEY_SHOW_COVERS -> callbacks.forEach {
- it.onShowCoverUpdate(showCovers)
- }
-
- KEY_QUALITY_COVERS -> callbacks.forEach {
- it.onQualityCoverUpdate(useQualityCovers)
- }
-
- KEY_LIB_TABS -> callbacks.forEach {
- it.onLibTabsUpdate(libTabs)
- }
-
- KEY_AUDIO_FOCUS -> callbacks.forEach {
- it.onAudioFocusUpdate(doAudioFocus)
- }
-
- KEY_REPLAY_GAIN -> callbacks.forEach {
- it.onReplayGainUpdate(replayGainMode)
- }
+ KEY_USE_ALT_NOTIFICATION_ACTION ->
+ callbacks.forEach { it.onNotifActionUpdate(useAltNotifAction) }
+ KEY_SHOW_COVERS -> callbacks.forEach { it.onShowCoverUpdate(showCovers) }
+ KEY_QUALITY_COVERS -> callbacks.forEach { it.onQualityCoverUpdate(useQualityCovers) }
+ KEY_LIB_TABS -> callbacks.forEach { it.onLibTabsUpdate(libTabs) }
+ KEY_AUDIO_FOCUS -> callbacks.forEach { it.onAudioFocusUpdate(doAudioFocus) }
+ KEY_REPLAY_GAIN -> callbacks.forEach { it.onReplayGainUpdate(replayGainMode) }
}
}
@@ -304,26 +288,21 @@ class SettingsManager private constructor(context: Context) :
const val KEY_DETAIL_ARTIST_SORT = "auxio_artist_sort"
const val KEY_DETAIL_GENRE_SORT = "auxio_genre_sort"
- @Volatile
- private var INSTANCE: SettingsManager? = null
+ @Volatile private var INSTANCE: SettingsManager? = null
/**
- * Init the single instance of [SettingsManager]. Done so that every object
- * can have access to it regardless of if it has a context.
+ * Init the single instance of [SettingsManager]. Done so that every object can have access
+ * to it regardless of if it has a context.
*/
fun init(context: Context): SettingsManager {
if (INSTANCE == null) {
- synchronized(this) {
- INSTANCE = SettingsManager(context)
- }
+ synchronized(this) { INSTANCE = SettingsManager(context) }
}
return getInstance()
}
- /**
- * Get the single instance of [SettingsManager].
- */
+ /** Get the single instance of [SettingsManager]. */
fun getInstance(): SettingsManager {
val instance = INSTANCE
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPrefDialog.kt b/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPrefDialog.kt
index 93bbd141e..abfa10492 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPrefDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPrefDialog.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * IntListPrefDialog.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.settings.pref
import android.os.Bundle
@@ -24,17 +23,15 @@ import androidx.preference.PreferenceFragmentCompat
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.ui.LifecycleDialog
-/**
- * The dialog shown whenever an [IntListPreference] is shown.
- */
+/** The dialog shown whenever an [IntListPreference] is shown. */
class IntListPrefDialog : LifecycleDialog() {
override fun onConfigDialog(builder: AlertDialog.Builder) {
// Since we have to store the preference key as an argument, we have to find the
// preference we need to use manually.
- val pref = requireNotNull(
- (parentFragment as PreferenceFragmentCompat).preferenceManager
- .findPreference(requireArguments().getString(ARG_KEY, null))
- )
+ val pref =
+ requireNotNull(
+ (parentFragment as PreferenceFragmentCompat).preferenceManager.findPreference<
+ IntListPreference>(requireArguments().getString(ARG_KEY, null)))
builder.setTitle(pref.title)
@@ -52,9 +49,7 @@ class IntListPrefDialog : LifecycleDialog() {
fun from(pref: IntListPreference): IntListPrefDialog {
return IntListPrefDialog().apply {
- arguments = Bundle().apply {
- putString(ARG_KEY, pref.key)
- }
+ arguments = Bundle().apply { putString(ARG_KEY, pref.key) }
}
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt
index 78bdc08b4..66d820968 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * IntListPreference.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.settings.pref
import android.content.Context
@@ -25,32 +24,34 @@ import androidx.preference.DialogPreference
import androidx.preference.Preference
import org.oxycblt.auxio.R
-class IntListPreference @JvmOverloads constructor(
+class IntListPreference
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = androidx.preference.R.attr.dialogPreferenceStyle,
defStyleRes: Int = 0
) : DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
// Reflect into Preference to get the (normally inaccessible) default value.
- private val defValueField = Preference::class.java.getDeclaredField("mDefaultValue").apply {
- isAccessible = true
- }
+ private val defValueField =
+ Preference::class.java.getDeclaredField("mDefaultValue").apply { isAccessible = true }
val entries: Array
val values: IntArray
private var currentValue: Int? = null
- private val defValue: Int get() = defValueField.get(this) as Int
+ private val defValue: Int
+ get() = defValueField.get(this) as Int
init {
- val prefAttrs = context.obtainStyledAttributes(
- attrs, R.styleable.IntListPreference, defStyleAttr, defStyleRes
- )
+ val prefAttrs =
+ context.obtainStyledAttributes(
+ attrs, R.styleable.IntListPreference, defStyleAttr, defStyleRes)
entries = prefAttrs.getTextArray(R.styleable.IntListPreference_entries)
- values = context.resources.getIntArray(
- prefAttrs.getResourceId(R.styleable.IntListPreference_entryValues, -1)
- )
+ values =
+ context.resources.getIntArray(
+ prefAttrs.getResourceId(R.styleable.IntListPreference_entryValues, -1))
prefAttrs.recycle()
@@ -80,9 +81,7 @@ class IntListPreference @JvmOverloads constructor(
return -1
}
- /**
- * Set a value using the index of it in [values]
- */
+ /** Set a value using the index of it in [values] */
fun setValueIndex(index: Int) {
setValue(values[index])
}
diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt
index db4204fee..d8170d753 100644
--- a/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt
+++ b/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt
@@ -1,3 +1,20 @@
+/*
+ * 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 .
+ */
+
package org.oxycblt.auxio.settings.pref
import android.content.Context
@@ -11,10 +28,12 @@ import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getDrawableSafe
/**
- * A [SwitchPreferenceCompat] that emulates the M3 switches until the design team
- * actually bothers to add them to MDC.
+ * A [SwitchPreferenceCompat] that emulates the M3 switches until the design team actually bothers
+ * to add them to MDC.
*/
-class M3SwitchPreference @JvmOverloads constructor(
+class M3SwitchPreference
+@JvmOverloads
+constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.switchPreferenceCompatStyle,
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt
index 7f8892f43..966f2cfd5 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/ActionMenu.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * ActionMenu.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import android.view.View
@@ -52,12 +51,11 @@ fun Fragment.newMenu(anchor: View, data: Item, flag: Int = ActionMenu.FLAG_NONE)
* @param activity [AppCompatActivity] required as both a context and ViewModelStore owner.
* @param anchor [View] This should be centered around
* @param data [Item] this menu corresponds to
- * @param flag Any extra flags to accompany the data. See [FLAG_NONE], [FLAG_IN_ALBUM], [FLAG_IN_ARTIST], [FLAG_IN_GENRE] for more details.
+ * @param flag Any extra flags to accompany the data. See [FLAG_NONE], [FLAG_IN_ALBUM],
+ * [FLAG_IN_ARTIST], [FLAG_IN_GENRE] for more details.
* @throws IllegalStateException When there is no menu for this specific datatype/flag
- * @author OxygenCobalt
- * TODO: Stop scrolling when a menu is open
- * TODO: Prevent duplicate menus from showing up
- * TODO: Maybe replace this with a bottom sheet?
+ * @author OxygenCobalt TODO: Stop scrolling when a menu is open TODO: Prevent duplicate menus from
+ * showing up TODO: Maybe replace this with a bottom sheet?
*/
class ActionMenu(
activity: AppCompatActivity,
@@ -98,9 +96,7 @@ class ActionMenu(
}
}
- /**
- * Figure out what menu to use here, based on the data & flags
- */
+ /** Figure out what menu to use here, based on the data & flags */
@MenuRes
private fun determineMenu(): Int {
return when (data) {
@@ -109,31 +105,23 @@ class ActionMenu(
FLAG_NONE, FLAG_IN_GENRE -> R.menu.menu_song_actions
FLAG_IN_ALBUM -> R.menu.menu_album_song_actions
FLAG_IN_ARTIST -> R.menu.menu_artist_song_actions
-
else -> -1
}
}
-
is Album -> {
when (flag) {
FLAG_NONE -> R.menu.menu_album_actions
FLAG_IN_ARTIST -> R.menu.menu_artist_album_actions
-
else -> -1
}
}
-
is Artist -> R.menu.menu_artist_actions
-
is Genre -> R.menu.menu_genre_actions
-
else -> -1
}
}
- /**
- * Determine what to do when a MenuItem is clicked.
- */
+ /** Determine what to do when a MenuItem is clicked. */
private fun onMenuClick(@IdRes id: Int) {
when (id) {
R.id.action_play -> {
@@ -141,59 +129,48 @@ class ActionMenu(
is Album -> playbackModel.playAlbum(data, false)
is Artist -> playbackModel.playArtist(data, false)
is Genre -> playbackModel.playGenre(data, false)
-
else -> {}
}
}
-
R.id.action_shuffle -> {
when (data) {
is Album -> playbackModel.playAlbum(data, true)
is Artist -> playbackModel.playArtist(data, true)
is Genre -> playbackModel.playGenre(data, true)
-
else -> {}
}
}
-
R.id.action_play_next -> {
when (data) {
is Song -> {
playbackModel.playNext(data)
context.showToast(R.string.lbl_queue_added)
}
-
is Album -> {
playbackModel.playNext(data)
context.showToast(R.string.lbl_queue_added)
}
-
else -> {}
}
}
-
R.id.action_queue_add -> {
when (data) {
is Song -> {
playbackModel.addToQueue(data)
context.showToast(R.string.lbl_queue_added)
}
-
is Album -> {
playbackModel.addToQueue(data)
context.showToast(R.string.lbl_queue_added)
}
-
else -> {}
}
}
-
R.id.action_go_album -> {
if (data is Song) {
detailModel.navToItem(data.album)
}
}
-
R.id.action_go_artist -> {
if (data is Song) {
detailModel.navToItem(data.album.artist)
@@ -205,13 +182,22 @@ class ActionMenu(
}
companion object {
- /** No Flags **/
+ /** No Flags */
const val FLAG_NONE = -1
- /** Flag for when a menu is opened from an artist (See [org.oxycblt.auxio.detail.ArtistDetailFragment]) **/
+ /**
+ * Flag for when a menu is opened from an artist (See
+ * [org.oxycblt.auxio.detail.ArtistDetailFragment])
+ */
const val FLAG_IN_ARTIST = 0
- /** Flag for when a menu is opened from an album (See [org.oxycblt.auxio.detail.AlbumDetailFragment]) **/
+ /**
+ * Flag for when a menu is opened from an album (See
+ * [org.oxycblt.auxio.detail.AlbumDetailFragment])
+ */
const val FLAG_IN_ALBUM = 1
- /** Flag for when a menu is opened from a genre (See [org.oxycblt.auxio.detail.GenreDetailFragment]) **/
+ /**
+ * Flag for when a menu is opened from a genre (See
+ * [org.oxycblt.auxio.detail.GenreDetailFragment])
+ */
const val FLAG_IN_GENRE = 2
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt b/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt
index 7aa7ccfda..dccde380f 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/DiffCallback.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * DiffCallback.kt is part of Auxio.
*
* 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
@@ -15,15 +14,15 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import androidx.recyclerview.widget.DiffUtil
import org.oxycblt.auxio.music.Item
/**
- * A re-usable diff callback for all [Item] implementations.
- * **Use this instead of creating a DiffCallback for each adapter.**
+ * A re-usable diff callback for all [Item] implementations. **Use this instead of creating a
+ * DiffCallback for each adapter.**
* @author OxygenCobalt
*/
class DiffCallback : DiffUtil.ItemCallback() {
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/DisplayMode.kt b/app/src/main/java/org/oxycblt/auxio/ui/DisplayMode.kt
index 92ed829c3..58fb9b8df 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/DisplayMode.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/DisplayMode.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * DisplayMode.kt is part of Auxio.
*
* 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
@@ -15,15 +14,15 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import org.oxycblt.auxio.R
/**
- * An enum for determining what items to show in a given list.
- * Note: **DO NOT RE-ARRANGE THE ENUM**. The ordinals are used to store library tabs, so doing
- * changing them would also change the meaning of tab instances.
+ * An enum for determining what items to show in a given list. Note: **DO NOT RE-ARRANGE THE ENUM**.
+ * The ordinals are used to store library tabs, so doing changing them would also change the meaning
+ * of tab instances.
* @author OxygenCobalt
*/
enum class DisplayMode {
@@ -32,19 +31,23 @@ enum class DisplayMode {
SHOW_ARTISTS,
SHOW_GENRES;
- val string: Int get() = when (this) {
- SHOW_SONGS -> R.string.lbl_songs
- SHOW_ALBUMS -> R.string.lbl_albums
- SHOW_ARTISTS -> R.string.lbl_artists
- SHOW_GENRES -> R.string.lbl_genres
- }
+ val string: Int
+ get() =
+ when (this) {
+ SHOW_SONGS -> R.string.lbl_songs
+ SHOW_ALBUMS -> R.string.lbl_albums
+ SHOW_ARTISTS -> R.string.lbl_artists
+ SHOW_GENRES -> R.string.lbl_genres
+ }
- val icon: Int get() = when (this) {
- SHOW_SONGS -> R.drawable.ic_song
- SHOW_ALBUMS -> R.drawable.ic_album
- SHOW_ARTISTS -> R.drawable.ic_artist
- SHOW_GENRES -> R.drawable.ic_genre
- }
+ val icon: Int
+ get() =
+ when (this) {
+ SHOW_SONGS -> R.drawable.ic_song
+ SHOW_ALBUMS -> R.drawable.ic_album
+ SHOW_ARTISTS -> R.drawable.ic_artist
+ SHOW_GENRES -> R.drawable.ic_genre
+ }
companion object {
private const val INT_NULL = 0xA107
@@ -54,8 +57,8 @@ enum class DisplayMode {
private const val INT_SHOW_SONGS = 0xA10B
/**
- * Convert this enum into an integer for filtering.
- * In this context, a null value means to filter nothing.
+ * Convert this enum into an integer for filtering. In this context, a null value means to
+ * filter nothing.
* @return An integer constant for that display mode, or a constant for a null [DisplayMode]
*/
fun toFilterInt(value: DisplayMode?): Int {
@@ -69,8 +72,8 @@ enum class DisplayMode {
}
/**
- * Convert a filtering integer to a [DisplayMode].
- * In this context, a null value means to filter nothing.
+ * Convert a filtering integer to a [DisplayMode]. In this context, a null value means to
+ * filter nothing.
* @return A [DisplayMode] for this constant (including null)
*/
fun fromFilterInt(value: Int): DisplayMode? {
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt
index f30302ca0..30decb333 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * LiftAppBarLayout.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import android.content.Context
@@ -33,32 +32,31 @@ import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.systemBarInsetsCompat
/**
- * An [AppBarLayout] that fixes a bug with the default implementation where the lifted state
- * will not properly respond to RecyclerView events.
- * **Note:** This layout relies on [AppBarLayout.liftOnScrollTargetViewId] to figure out what
- * scrolling view to use. Failure to specify this will result in the layout not working.
+ * An [AppBarLayout] that fixes a bug with the default implementation where the lifted state will
+ * not properly respond to RecyclerView events. **Note:** This layout relies on
+ * [AppBarLayout.liftOnScrollTargetViewId] to figure out what scrolling view to use. Failure to
+ * specify this will result in the layout not working.
*/
-open class EdgeAppBarLayout @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : AppBarLayout(context, attrs, defStyleAttr) {
+open class EdgeAppBarLayout
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ AppBarLayout(context, attrs, defStyleAttr) {
private var scrollingChild: View? = null
private val tConsumed = IntArray(2)
- private val onPreDraw = ViewTreeObserver.OnPreDrawListener {
- val child = findScrollingChild()
+ private val onPreDraw =
+ ViewTreeObserver.OnPreDrawListener {
+ val child = findScrollingChild()
- if (child != null) {
- val coordinator = parent as CoordinatorLayout
- (layoutParams as CoordinatorLayout.LayoutParams).behavior?.onNestedPreScroll(
- coordinator, this, coordinator, 0, 0, tConsumed, 0
- )
+ if (child != null) {
+ val coordinator = parent as CoordinatorLayout
+ (layoutParams as CoordinatorLayout.LayoutParams).behavior?.onNestedPreScroll(
+ coordinator, this, coordinator, 0, 0, tConsumed, 0)
+ }
+
+ true
}
- true
- }
-
init {
viewTreeObserver.addOnPreDrawListener(onPreDraw)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt
index 84341f7be..87b8996ff 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeCoordinatorLayout.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * FuckedCoordinatorLayout.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import android.content.Context
@@ -26,16 +25,15 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.children
/**
- * Class that fixes an issue where [CoordinatorLayout] will override [onApplyWindowInsets]
- * and delegate the job to ***LAYOUT BEHAVIOR INSTANCES*** instead of the actual views.
+ * Class that fixes an issue where [CoordinatorLayout] will override [onApplyWindowInsets] and
+ * delegate the job to ***LAYOUT BEHAVIOR INSTANCES*** instead of the actual views.
*
* I can't believe I have to do this.
*/
-class EdgeCoordinatorLayout @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : CoordinatorLayout(context, attrs, defStyleAttr) {
+class EdgeCoordinatorLayout
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ CoordinatorLayout(context, attrs, defStyleAttr) {
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
for (child in children) {
child.onApplyWindowInsets(insets)
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt
index 3a273a55b..fbda7331e 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeRecyclerView.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * EdgeRecyclerView.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import android.content.Context
@@ -26,14 +25,11 @@ import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.util.systemBarInsetsCompat
-/**
- * A [RecyclerView] that automatically applies insets to itself.
- */
-class EdgeRecyclerView @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- @AttrRes defStyleAttr: Int = 0
-) : RecyclerView(context, attrs, defStyleAttr) {
+/** A [RecyclerView] that automatically applies insets to itself. */
+class EdgeRecyclerView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
+ RecyclerView(context, attrs, defStyleAttr) {
override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
updatePadding(bottom = insets.systemBarInsetsCompat.bottom)
return insets
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/LifecycleDialog.kt b/app/src/main/java/org/oxycblt/auxio/ui/LifecycleDialog.kt
index 519bfbd46..c05c5e491 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/LifecycleDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/LifecycleDialog.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * LifecycleDialog.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import android.app.Dialog
@@ -27,8 +26,8 @@ import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
/**
- * A wrapper around [DialogFragment] that allows the usage of the standard Auxio lifecycle
- * override [onCreateView] and [onDestroyView], but with a proper dialog being created.
+ * A wrapper around [DialogFragment] that allows the usage of the standard Auxio lifecycle override
+ * [onCreateView] and [onDestroyView], but with a proper dialog being created.
*/
abstract class LifecycleDialog : AppCompatDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt
index d92075c10..d82026d36 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Sort.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import androidx.annotation.IdRes
@@ -53,15 +52,15 @@ sealed class Sort(open val isAscending: Boolean) {
/** Sort by the year of an item, only supported by [Album] and [Song] */
class ByYear(override val isAscending: Boolean) : Sort(isAscending)
- /**
- * Get the corresponding item id for this sort.
- */
- val itemId: Int get() = when (this) {
- is ByName -> R.id.option_sort_name
- is ByArtist -> R.id.option_sort_artist
- is ByAlbum -> R.id.option_sort_album
- is ByYear -> R.id.option_sort_year
- }
+ /** Get the corresponding item id for this sort. */
+ val itemId: Int
+ get() =
+ when (this) {
+ is ByName -> R.id.option_sort_name
+ is ByArtist -> R.id.option_sort_artist
+ is ByAlbum -> R.id.option_sort_album
+ is ByYear -> R.id.option_sort_year
+ }
/**
* Apply [ascending] to the status of this sort.
@@ -100,10 +99,10 @@ sealed class Sort(open val isAscending: Boolean) {
fun sortSongs(songs: Collection): List {
return when (this) {
is ByName -> songs.stringSort { it.name }
-
- else -> sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
- album.songs.intSort(true) { it.track ?: 0 }
- }
+ else ->
+ sortAlbums(songs.groupBy { it.album }.keys).flatMap { album ->
+ album.songs.intSort(true) { it.track ?: 0 }
+ }
}
}
@@ -117,10 +116,10 @@ sealed class Sort(open val isAscending: Boolean) {
fun sortAlbums(albums: Collection): List {
return when (this) {
is ByName, is ByAlbum -> albums.stringSort { it.resolvedName }
-
- is ByArtist -> sortParents(albums.groupBy { it.artist }.keys)
- .flatMap { ByYear(false).sortAlbums(it.albums) }
-
+ is ByArtist ->
+ sortParents(albums.groupBy { it.artist }.keys).flatMap {
+ ByYear(false).sortAlbums(it.albums)
+ }
is ByYear -> albums.intSort { it.year ?: 0 }
}
}
@@ -158,9 +157,7 @@ sealed class Sort(open val isAscending: Boolean) {
return sortSongs(genre.songs)
}
- /**
- * Convert this sort to it's integer representation.
- */
+ /** Convert this sort to it's integer representation. */
fun toInt(): Int {
return when (this) {
is ByName -> INT_NAME
@@ -175,15 +172,14 @@ sealed class Sort(open val isAscending: Boolean) {
selector: (T) -> String
): List {
// Chain whatever item call with sliceArticle for correctness
- val chained: (T) -> String = {
- selector(it).sliceArticle()
- }
+ val chained: (T) -> String = { selector(it).sliceArticle() }
- val comparator = if (asc) {
- compareBy(String.CASE_INSENSITIVE_ORDER, chained)
- } else {
- compareByDescending(String.CASE_INSENSITIVE_ORDER, chained)
- }
+ val comparator =
+ if (asc) {
+ compareBy(String.CASE_INSENSITIVE_ORDER, chained)
+ } else {
+ compareByDescending(String.CASE_INSENSITIVE_ORDER, chained)
+ }
return sortedWith(comparator)
}
@@ -192,11 +188,12 @@ sealed class Sort(open val isAscending: Boolean) {
asc: Boolean = isAscending,
selector: (T) -> Int,
): List {
- val comparator = if (asc) {
- compareBy(selector)
- } else {
- compareByDescending(selector)
- }
+ val comparator =
+ if (asc) {
+ compareBy(selector)
+ } else {
+ compareByDescending(selector)
+ }
return sortedWith(comparator)
}
@@ -227,9 +224,9 @@ sealed class Sort(open val isAscending: Boolean) {
}
/**
- * Slice a string so that any preceding articles like The/A(n) are truncated.
- * This is hilariously anglo-centric, but its mostly for MediaStore compat and hopefully
- * shouldn't run with other languages.
+ * Slice a string so that any preceding articles like The/A(n) are truncated. This is hilariously
+ * anglo-centric, but its mostly for MediaStore compat and hopefully shouldn't run with other
+ * languages.
*/
fun String.sliceArticle(): String {
if (length > 5 && startsWith("the ", true)) {
diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt
index 975c23ca1..d36e9983d 100644
--- a/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt
+++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewHolders.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * SortHeaderViewHolder.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.ui
import android.content.Context
@@ -53,22 +52,18 @@ abstract class BaseViewHolder(
) : RecyclerView.ViewHolder(binding.root) {
init {
// Force the layout to *actually* be the screen width
- binding.root.layoutParams = RecyclerView.LayoutParams(
- RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT
- )
+ binding.root.layoutParams =
+ RecyclerView.LayoutParams(
+ RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
}
/**
- * Bind the viewholder with whatever [Item] instance that has been specified.
- * Will call [onBind] on the inheriting ViewHolder.
+ * Bind the viewholder with whatever [Item] instance that has been specified. Will call [onBind]
+ * on the inheriting ViewHolder.
* @param data Data that the viewholder should be bound with
*/
fun bind(data: T) {
- doOnClick?.let { onClick ->
- binding.root.setOnClickListener {
- onClick(data)
- }
- }
+ doOnClick?.let { onClick -> binding.root.setOnClickListener { onClick(data) } }
doOnLongClick?.let { onLongClick ->
binding.root.setOnLongClickListener { view ->
@@ -84,16 +79,15 @@ abstract class BaseViewHolder(
}
/**
- * Function that performs binding operations unique to the inheriting viewholder.
- * Add any specialized code to an override of this instead of [BaseViewHolder] itself.
+ * Function that performs binding operations unique to the inheriting viewholder. Add any
+ * specialized code to an override of this instead of [BaseViewHolder] itself.
*/
protected abstract fun onBind(data: T)
}
-/**
- * The Shared ViewHolder for a [Song]. Instantiation should be done with [from].
- */
-class SongViewHolder private constructor(
+/** The Shared ViewHolder for a [Song]. Instantiation should be done with [from]. */
+class SongViewHolder
+private constructor(
private val binding: ItemSongBinding,
doOnClick: (data: Song) -> Unit,
doOnLongClick: (view: View, data: Song) -> Unit
@@ -109,26 +103,21 @@ class SongViewHolder private constructor(
companion object {
const val ITEM_TYPE = 0xA000
- /**
- * Create an instance of [SongViewHolder]
- */
+ /** Create an instance of [SongViewHolder] */
fun from(
context: Context,
doOnClick: (data: Song) -> Unit,
doOnLongClick: (view: View, data: Song) -> Unit
): SongViewHolder {
return SongViewHolder(
- ItemSongBinding.inflate(context.inflater),
- doOnClick, doOnLongClick
- )
+ ItemSongBinding.inflate(context.inflater), doOnClick, doOnLongClick)
}
}
}
-/**
- * The Shared ViewHolder for a [Album]. Instantiation should be done with [from].
- */
-class AlbumViewHolder private constructor(
+/** The Shared ViewHolder for a [Album]. Instantiation should be done with [from]. */
+class AlbumViewHolder
+private constructor(
private val binding: ItemAlbumBinding,
doOnClick: (data: Album) -> Unit,
doOnLongClick: (view: View, data: Album) -> Unit
@@ -142,26 +131,21 @@ class AlbumViewHolder private constructor(
companion object {
const val ITEM_TYPE = 0xA001
- /**
- * Create an instance of [AlbumViewHolder]
- */
+ /** Create an instance of [AlbumViewHolder] */
fun from(
context: Context,
doOnClick: (data: Album) -> Unit,
doOnLongClick: (view: View, data: Album) -> Unit
): AlbumViewHolder {
return AlbumViewHolder(
- ItemAlbumBinding.inflate(context.inflater),
- doOnClick, doOnLongClick
- )
+ ItemAlbumBinding.inflate(context.inflater), doOnClick, doOnLongClick)
}
}
}
-/**
- * The Shared ViewHolder for a [Artist]. Instantiation should be done with [from].
- */
-class ArtistViewHolder private constructor(
+/** The Shared ViewHolder for a [Artist]. Instantiation should be done with [from]. */
+class ArtistViewHolder
+private constructor(
private val binding: ItemArtistBinding,
doOnClick: (Artist) -> Unit,
doOnLongClick: (view: View, data: Artist) -> Unit
@@ -175,26 +159,21 @@ class ArtistViewHolder private constructor(
companion object {
const val ITEM_TYPE = 0xA002
- /**
- * Create an instance of [ArtistViewHolder]
- */
+ /** Create an instance of [ArtistViewHolder] */
fun from(
context: Context,
doOnClick: (Artist) -> Unit,
doOnLongClick: (view: View, data: Artist) -> Unit
): ArtistViewHolder {
return ArtistViewHolder(
- ItemArtistBinding.inflate(context.inflater),
- doOnClick, doOnLongClick
- )
+ ItemArtistBinding.inflate(context.inflater), doOnClick, doOnLongClick)
}
}
}
-/**
- * The Shared ViewHolder for a [Genre]. Instantiation should be done with [from].
- */
-class GenreViewHolder private constructor(
+/** The Shared ViewHolder for a [Genre]. Instantiation should be done with [from]. */
+class GenreViewHolder
+private constructor(
private val binding: ItemGenreBinding,
doOnClick: (Genre) -> Unit,
doOnLongClick: (view: View, data: Genre) -> Unit
@@ -208,28 +187,21 @@ class GenreViewHolder private constructor(
companion object {
const val ITEM_TYPE = 0xA003
- /**
- * Create an instance of [GenreViewHolder]
- */
+ /** Create an instance of [GenreViewHolder] */
fun from(
context: Context,
doOnClick: (Genre) -> Unit,
doOnLongClick: (view: View, data: Genre) -> Unit
): GenreViewHolder {
return GenreViewHolder(
- ItemGenreBinding.inflate(context.inflater),
- doOnClick, doOnLongClick
- )
+ ItemGenreBinding.inflate(context.inflater), doOnClick, doOnLongClick)
}
}
}
-/**
- * The Shared ViewHolder for a [Header]. Instantiation should be done with [from]
- */
-class HeaderViewHolder private constructor(
- private val binding: ItemHeaderBinding
-) : BaseViewHolder(binding) {
+/** The Shared ViewHolder for a [Header]. Instantiation should be done with [from] */
+class HeaderViewHolder private constructor(private val binding: ItemHeaderBinding) :
+ BaseViewHolder(binding) {
override fun onBind(data: Header) {
binding.header = data
@@ -238,23 +210,16 @@ class HeaderViewHolder private constructor(
companion object {
const val ITEM_TYPE = 0xA004
- /**
- * Create an instance of [HeaderViewHolder]
- */
+ /** Create an instance of [HeaderViewHolder] */
fun from(context: Context): HeaderViewHolder {
- return HeaderViewHolder(
- ItemHeaderBinding.inflate(context.inflater)
- )
+ return HeaderViewHolder(ItemHeaderBinding.inflate(context.inflater))
}
}
}
-/**
- * The Shared ViewHolder for an [ActionHeader]. Instantiation should be done with [from]
- */
-class ActionHeaderViewHolder private constructor(
- private val binding: ItemActionHeaderBinding
-) : BaseViewHolder(binding) {
+/** The Shared ViewHolder for an [ActionHeader]. Instantiation should be done with [from] */
+class ActionHeaderViewHolder private constructor(private val binding: ItemActionHeaderBinding) :
+ BaseViewHolder(binding) {
override fun onBind(data: ActionHeader) {
binding.header = data
@@ -271,9 +236,7 @@ class ActionHeaderViewHolder private constructor(
companion object {
const val ITEM_TYPE = 0xA005
- /**
- * Create an instance of [ActionHeaderViewHolder]
- */
+ /** Create an instance of [ActionHeaderViewHolder] */
fun from(context: Context): ActionHeaderViewHolder {
return ActionHeaderViewHolder(ItemActionHeaderBinding.inflate(context.inflater))
}
diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt
index e075552a8..043d2fd63 100644
--- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt
+++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * ContextUtil.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.util
import android.app.PendingIntent
@@ -39,30 +38,28 @@ import androidx.annotation.PluralsRes
import androidx.annotation.Px
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
-import org.oxycblt.auxio.MainActivity
import kotlin.reflect.KClass
import kotlin.system.exitProcess
+import org.oxycblt.auxio.MainActivity
const val INTENT_REQUEST_CODE = 0xA0A0
-/**
- * Shortcut to get a [LayoutInflater] from a [Context]
- */
-val Context.inflater: LayoutInflater get() = LayoutInflater.from(this)
+/** Shortcut to get a [LayoutInflater] from a [Context] */
+val Context.inflater: LayoutInflater
+ get() = LayoutInflater.from(this)
/**
- * Returns whether the current UI is in night mode or not. This will work if the theme is
- * automatic as well.
+ * Returns whether the current UI is in night mode or not. This will work if the theme is automatic
+ * as well.
*/
-val Context.isNight: Boolean get() =
- resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
- Configuration.UI_MODE_NIGHT_YES
+val Context.isNight: Boolean
+ get() =
+ resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+ Configuration.UI_MODE_NIGHT_YES
-/**
- * Returns if this device is in landscape.
- */
-val Context.isLandscape get() =
- resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+/** Returns if this device is in landscape. */
+val Context.isLandscape
+ get() = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
/**
* Convenience method for getting a plural.
@@ -117,11 +114,12 @@ fun Context.getAttrColorSafe(@AttrRes attr: Int): Int {
theme.resolveAttribute(attr, resolvedAttr, true)
// Then convert it to a proper color
- val color = if (resolvedAttr.resourceId != 0) {
- resolvedAttr.resourceId
- } else {
- resolvedAttr.data
- }
+ val color =
+ if (resolvedAttr.resourceId != 0) {
+ resolvedAttr.resourceId
+ } else {
+ resolvedAttr.data
+ }
return getColorSafe(color)
}
@@ -183,9 +181,8 @@ fun Context.getDimenOffsetSafe(@DimenRes dimen: Int): Int {
@Px
fun Context.pxOfDp(@Dimension dp: Float): Int {
- return TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics
- ).toInt()
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics)
+ .toInt()
}
private fun Context.handleResourceFailure(e: Exception, what: String, default: T): T {
@@ -207,46 +204,37 @@ fun Context.getSystemServiceSafe(serviceClass: KClass): T {
}
}
-/**
- * Create a toast using the provided string resource.
- */
+/** Create a toast using the provided string resource. */
fun Context.showToast(@StringRes str: Int) {
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
}
-/**
- * Create a [PendingIntent] that leads to Auxio's [MainActivity]
- */
+/** Create a [PendingIntent] that leads to Auxio's [MainActivity] */
fun Context.newMainIntent(): PendingIntent {
return PendingIntent.getActivity(
- this, INTENT_REQUEST_CODE, Intent(this, MainActivity::class.java),
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
- PendingIntent.FLAG_IMMUTABLE
- else 0
- )
+ this,
+ INTENT_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]
- */
+/** Create a broadcast [PendingIntent] */
fun Context.newBroadcastIntent(what: String): PendingIntent {
return PendingIntent.getBroadcast(
- this, INTENT_REQUEST_CODE, Intent(what),
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
- PendingIntent.FLAG_IMMUTABLE
- else 0
- )
+ this,
+ INTENT_REQUEST_CODE,
+ Intent(what),
+ 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.
- */
+/** Hard-restarts the app. Useful for forcing the app to reload music. */
fun Context.hardRestart() {
// Instead of having to do a ton of cleanup and horrible code changes
// to restart this application non-destructively, I just restart the UI task [There is only
// one, after all] and then kill the application using exitProcess. Works well enough.
- val intent = Intent(applicationContext, MainActivity::class.java)
- .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ val intent =
+ Intent(applicationContext, MainActivity::class.java)
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
startActivity(intent)
exitProcess(0)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt
index f47c88d32..4361722f3 100644
--- a/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt
+++ b/app/src/main/java/org/oxycblt/auxio/util/DbUtil.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * DbUtil.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.util
import android.database.Cursor
@@ -23,15 +22,13 @@ import android.database.sqlite.SQLiteDatabase
import android.os.Looper
/**
- * Shortcut for querying all items in a database and running [block] with the cursor returned.
- * Will not run if the cursor is null.
+ * Shortcut for querying all items in a database and running [block] with the cursor returned. Will
+ * not run if the cursor is null.
*/
fun SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R) =
query(tableName, null, null, null, null, null, null)?.use(block)
-/**
- * Assert that we are on a background thread.
- */
+/** Assert that we are on a background thread. */
fun assertBackgroundThread() {
check(Looper.myLooper() != Looper.getMainLooper()) {
"This operation must be ran on a background thread"
diff --git a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt
index 500b65df9..2727cb9cb 100644
--- a/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt
+++ b/app/src/main/java/org/oxycblt/auxio/util/LogUtil.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * LogUtil.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.util
import android.util.Log
@@ -25,14 +24,16 @@ import org.oxycblt.auxio.BuildConfig
// Yes, I know timber exists but this does what I need.
/**
- * Shortcut method for logging a non-string [obj] to debug. Should only be used for debug preferably.
+ * Shortcut method for logging a non-string [obj] to debug. Should only be used for debug
+ * preferably.
*/
fun Any.logD(obj: Any) {
logD(obj.toString())
}
/**
- * Shortcut method for logging [msg] to the debug console. Handles debug builds and anonymous objects
+ * Shortcut method for logging [msg] to the debug console. Handles debug builds and anonymous
+ * objects
*/
fun Any.logD(msg: String) {
if (BuildConfig.DEBUG) {
@@ -41,16 +42,12 @@ fun Any.logD(msg: String) {
}
}
-/**
- * Shortcut method for logging [msg] as a warning to the console. Handles anonymous objects
- */
+/** Shortcut method for logging [msg] as a warning to the console. Handles anonymous objects */
fun Any.logW(msg: String) {
Log.w(getName(), msg)
}
-/**
- * Shortcut method for logging [msg] as an error to the console. Handles anonymous objects
- */
+/** Shortcut method for logging [msg] as an error to the console. Handles anonymous objects */
fun Any.logE(msg: String) {
Log.e(getName(), msg)
}
@@ -77,18 +74,16 @@ private fun Any.getName(): String = "Auxio.${this::class.simpleName ?: "Anonymou
* I know that this will not stop you, but consider what you are doing with your life, plagiarizers.
* Do you want to live a fulfilling existence on this planet? Or do you want to spend your life
* taking work others did and making it objectively worse so you could arbitrage a fraction of a
- * penny on every AdMob impression you get? You could do so many great things if you simply had
- * the courage to come up with an idea of your own. If you still want to go on, I guess the only
- * thing I can say is this: JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE 六四事件
+ * penny on every AdMob impression you get? You could do so many great things if you simply had the
+ * courage to come up with an idea of your own. If you still want to go on, I guess the only thing I
+ * can say is this: JUNE 1989 TIANAMEN SQUARE PROTESTS AND MASSACRE 六四事件
*/
private fun basedCopyleftNotice() {
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 modifications must be made open source!"
- )
+ "GPLv3 and all modifications must be made open source!")
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt
index 7f1c9773e..ea7fae6c7 100644
--- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt
+++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * ViewUtil.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.util
import android.content.res.ColorStateList
@@ -29,10 +28,9 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
-/**
- * Converts this color to a single-color [ColorStateList].
- */
-val @receiver:ColorRes Int.stateList get() = ColorStateList.valueOf(this)
+/** Converts this color to a single-color [ColorStateList]. */
+val @receiver:ColorRes Int.stateList
+ get() = ColorStateList.valueOf(this)
/**
* Apply the recommended spans for a [RecyclerView].
@@ -47,25 +45,24 @@ fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) {
val mgr = GridLayoutManager(context, spans)
if (shouldBeFullWidth != null) {
- mgr.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
- override fun getSpanSize(position: Int): Int {
- return if (shouldBeFullWidth(position)) spans else 1
+ mgr.spanSizeLookup =
+ object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return if (shouldBeFullWidth(position)) spans else 1
+ }
}
- }
}
layoutManager = mgr
}
}
-/**
- * Returns whether a recyclerview can scroll.
- */
+/** Returns whether a recyclerview can scroll. */
fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height
/**
- * Disables drop shadows on a view programmatically in a version-compatible manner.
- * This only works on Android 9 and above. Below that version, shadows will remain visible.
+ * Disables drop shadows on a view programmatically in a version-compatible manner. This only works
+ * on Android 9 and above. Below that version, shadows will remain visible.
*/
fun View.disableDropShadowCompat() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@@ -77,49 +74,44 @@ fun View.disableDropShadowCompat() {
}
/**
- * Resolve system bar insets in a version-aware manner. This can be used to apply padding to
- * a view that properly follows all the frustrating changes that were made between 8-11.
+ * Resolve system bar insets in a version-aware manner. This can be used to apply padding to a view
+ * that properly follows all the frustrating changes that were made between 8-11.
*/
-val WindowInsets.systemBarInsetsCompat: Rect get() {
- return when {
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
- getInsets(WindowInsets.Type.systemBars()).run {
- Rect(left, top, right, bottom)
+val WindowInsets.systemBarInsetsCompat: Rect
+ get() {
+ return when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
+ getInsets(WindowInsets.Type.systemBars()).run { Rect(left, top, right, bottom) }
+ }
+ else -> {
+ @Suppress("DEPRECATION")
+ Rect(
+ systemWindowInsetLeft,
+ systemWindowInsetTop,
+ systemWindowInsetRight,
+ systemWindowInsetBottom)
}
}
-
- else -> {
- @Suppress("DEPRECATION")
- Rect(
- systemWindowInsetLeft,
- systemWindowInsetTop,
- systemWindowInsetRight,
- systemWindowInsetBottom
- )
- }
}
-}
/**
* Replaces the system bar insets in a version-aware manner. This can be used to modify the insets
* for child views in a way that follows all of the frustrating changes that were made between 8-11.
*/
-fun WindowInsets.replaceSystemBarInsetsCompat(left: Int, top: Int, right: Int, bottom: Int): WindowInsets {
+fun WindowInsets.replaceSystemBarInsetsCompat(
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int
+): WindowInsets {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
WindowInsets.Builder(this)
- .setInsets(
- WindowInsets.Type.systemBars(),
- Insets.of(left, top, right, bottom)
- )
+ .setInsets(WindowInsets.Type.systemBars(), Insets.of(left, top, right, bottom))
.build()
}
-
else -> {
- @Suppress("DEPRECATION")
- replaceSystemWindowInsets(
- left, top, right, bottom
- )
+ @Suppress("DEPRECATION") replaceSystemWindowInsets(left, top, right, bottom)
}
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt
index feb146d7a..9bb15a4e2 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/Forms.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * Forms.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.widgets
import android.content.Context
@@ -28,8 +27,8 @@ import org.oxycblt.auxio.util.newBroadcastIntent
import org.oxycblt.auxio.util.newMainIntent
/**
- * The default widget is displayed whenever there is no music playing. It just shows the
- * message "No music playing".
+ * The default widget is displayed whenever there is no music playing. It just shows the message "No
+ * music playing".
*/
fun createDefaultWidget(context: Context): RemoteViews {
return createViews(context, R.layout.widget_default)
@@ -46,9 +45,9 @@ fun createTinyWidget(context: Context, state: WidgetState): RemoteViews {
}
/**
- * The small widget is for 2x2 widgets and just shows the cover art and playback controls.
- * This is generally because a Medium widget is too large for this widget size and a text-only
- * widget is too small for this widget size.
+ * The small widget is for 2x2 widgets and just shows the cover art and playback controls. This is
+ * generally because a Medium widget is too large for this widget size and a text-only widget is too
+ * small for this widget size.
*/
fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_small)
@@ -57,8 +56,8 @@ fun createSmallWidget(context: Context, state: WidgetState): RemoteViews {
}
/**
- * The medium widget is for 2x3 widgets and shows the cover art, title/artist, and three
- * controls. This is the default widget configuration.
+ * The medium widget is for 2x3 widgets and shows the cover art, title/artist, and three controls.
+ * This is the default widget configuration.
*/
fun createMediumWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_medium)
@@ -66,34 +65,24 @@ fun createMediumWidget(context: Context, state: WidgetState): RemoteViews {
.applyBasicControls(context, state)
}
-/**
- * The wide widget is for Nx2 widgets and is like the small widget but with more controls.
- */
+/** The wide widget is for Nx2 widgets and is like the small widget but with more controls. */
fun createWideWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_wide)
.applyCover(context, state)
.applyFullControls(context, state)
}
-/**
- * The large widget is for 3x4 widgets and shows all metadata and controls.
- */
+/** The large widget is for 3x4 widgets and shows all metadata and controls. */
fun createLargeWidget(context: Context, state: WidgetState): RemoteViews {
return createViews(context, R.layout.widget_large)
.applyMeta(context, state)
.applyFullControls(context, state)
}
-private fun createViews(
- context: Context,
- @LayoutRes layout: Int
-): RemoteViews {
+private fun createViews(context: Context, @LayoutRes layout: Int): RemoteViews {
val views = RemoteViews(context.packageName, layout)
- views.setOnClickPendingIntent(
- android.R.id.background,
- context.newMainIntent()
- )
+ views.setOnClickPendingIntent(android.R.id.background, context.newMainIntent())
return views
}
@@ -112,8 +101,7 @@ private fun RemoteViews.applyCover(context: Context, state: WidgetState): Remote
setImageViewBitmap(R.id.widget_cover, state.albumArt)
setContentDescription(
R.id.widget_cover,
- context.getString(R.string.desc_album_cover, state.song.resolvedAlbumName)
- )
+ context.getString(R.string.desc_album_cover, state.song.resolvedAlbumName))
} else {
setImageViewResource(R.id.widget_cover, R.drawable.ic_widget_album)
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
@@ -124,11 +112,7 @@ private fun RemoteViews.applyCover(context: Context, state: WidgetState): Remote
private fun RemoteViews.applyPlayControls(context: Context, state: WidgetState): RemoteViews {
setOnClickPendingIntent(
- R.id.widget_play_pause,
- context.newBroadcastIntent(
- PlaybackService.ACTION_PLAY_PAUSE
- )
- )
+ R.id.widget_play_pause, context.newBroadcastIntent(PlaybackService.ACTION_PLAY_PAUSE))
setImageViewResource(
R.id.widget_play_pause,
@@ -136,8 +120,7 @@ private fun RemoteViews.applyPlayControls(context: Context, state: WidgetState):
R.drawable.ic_pause
} else {
R.drawable.ic_play
- }
- )
+ })
return this
}
@@ -146,18 +129,10 @@ private fun RemoteViews.applyBasicControls(context: Context, state: WidgetState)
applyPlayControls(context, state)
setOnClickPendingIntent(
- R.id.widget_skip_prev,
- context.newBroadcastIntent(
- PlaybackService.ACTION_SKIP_PREV
- )
- )
+ R.id.widget_skip_prev, context.newBroadcastIntent(PlaybackService.ACTION_SKIP_PREV))
setOnClickPendingIntent(
- R.id.widget_skip_next,
- context.newBroadcastIntent(
- PlaybackService.ACTION_SKIP_NEXT
- )
- )
+ R.id.widget_skip_next, context.newBroadcastIntent(PlaybackService.ACTION_SKIP_NEXT))
return this
}
@@ -166,31 +141,25 @@ private fun RemoteViews.applyFullControls(context: Context, state: WidgetState):
applyBasicControls(context, state)
setOnClickPendingIntent(
- R.id.widget_loop,
- context.newBroadcastIntent(
- PlaybackService.ACTION_LOOP
- )
- )
+ R.id.widget_loop, context.newBroadcastIntent(PlaybackService.ACTION_LOOP))
setOnClickPendingIntent(
- R.id.widget_shuffle,
- context.newBroadcastIntent(
- PlaybackService.ACTION_SHUFFLE
- )
- )
+ R.id.widget_shuffle, context.newBroadcastIntent(PlaybackService.ACTION_SHUFFLE))
// Like notifications, use the remote variants of icons since we really don't want to hack
// indicators.
- val shuffleRes = when {
- state.isShuffled -> R.drawable.ic_remote_shuffle_on
- else -> R.drawable.ic_remote_shuffle_off
- }
+ val shuffleRes =
+ when {
+ state.isShuffled -> R.drawable.ic_remote_shuffle_on
+ else -> R.drawable.ic_remote_shuffle_off
+ }
- val loopRes = when (state.loopMode) {
- LoopMode.NONE -> R.drawable.ic_remote_loop_off
- LoopMode.ALL -> R.drawable.ic_loop_on
- LoopMode.TRACK -> R.drawable.ic_loop_one
- }
+ val loopRes =
+ when (state.loopMode) {
+ LoopMode.NONE -> R.drawable.ic_remote_loop_off
+ LoopMode.ALL -> R.drawable.ic_loop_on
+ LoopMode.TRACK -> R.drawable.ic_loop_one
+ }
setImageViewResource(R.id.widget_shuffle, shuffleRes)
setImageViewResource(R.id.widget_loop, loopRes)
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt
index 10bd5026c..7f48a1daf 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetController.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * WidgetController.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.widgets
import android.content.Context
@@ -28,12 +27,11 @@ import org.oxycblt.auxio.util.logD
/**
* A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the
* widget state based off of that. This cannot be rolled into [WidgetProvider] directly, as it may
- * result in memory leaks if [PlaybackStateManager]/[SettingsManager] gets created and bound
- * to without being released.
+ * result in memory leaks if [PlaybackStateManager]/[SettingsManager] gets created and bound to
+ * without being released.
*/
class WidgetController(private val context: Context) :
- PlaybackStateManager.Callback,
- SettingsManager.Callback {
+ PlaybackStateManager.Callback, SettingsManager.Callback {
private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val widget = WidgetProvider()
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
index 96dbf54ed..486911fc0 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * WidgetProvider.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.widgets
import android.appwidget.AppWidgetHostView
@@ -33,6 +32,7 @@ import androidx.core.graphics.drawable.toBitmap
import coil.imageLoader
import coil.request.ImageRequest
import coil.transform.RoundedCornersTransformation
+import kotlin.math.min
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.coil.SquareFrameTransform
import org.oxycblt.auxio.music.Song
@@ -41,7 +41,6 @@ import org.oxycblt.auxio.util.getDimenSizeSafe
import org.oxycblt.auxio.util.isLandscape
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
-import kotlin.math.min
/**
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively
@@ -67,38 +66,36 @@ class WidgetProvider : AppWidgetProvider() {
}
loadWidgetBitmap(context, song) { bitmap ->
- val state = WidgetState(
- song,
- bitmap,
- playbackManager.isPlaying,
- playbackManager.isShuffling,
- playbackManager.loopMode
- )
+ val state =
+ WidgetState(
+ song,
+ bitmap,
+ playbackManager.isPlaying,
+ playbackManager.isShuffling,
+ playbackManager.loopMode)
// 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(180f, 152f) to createSmallWidget(context, state),
- SizeF(272f, 152f) to createWideWidget(context, state),
- SizeF(180f, 270f) to createMediumWidget(context, state),
- SizeF(272f, 270f) to createLargeWidget(context, state)
- )
+ val views =
+ mapOf(
+ SizeF(180f, 100f) to createTinyWidget(context, state),
+ SizeF(180f, 152f) to createSmallWidget(context, state),
+ SizeF(272f, 152f) to createWideWidget(context, state),
+ SizeF(180f, 270f) to createMediumWidget(context, state),
+ SizeF(272f, 270f) to createLargeWidget(context, state))
appWidgetManager.applyViewsCompat(context, views)
}
}
/**
- * Custom function for loading bitmaps to the widget in a way that works with the
- * widget ImageView instances.
+ * Custom function for loading bitmaps to the widget in a way that works with the widget
+ * ImageView instances.
*/
private fun loadWidgetBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) {
- val coverRequest = ImageRequest.Builder(context)
- .data(song.album)
- .target(
- onError = { onDone(null) },
- onSuccess = { onDone(it.toBitmap()) }
- )
+ val coverRequest =
+ ImageRequest.Builder(context)
+ .data(song.album)
+ .target(onError = { onDone(null) }, onSuccess = { onDone(it.toBitmap()) })
// The widget has two distinct styles that we must transform the album art to accommodate:
// - Before Android 12, the widget has hard edges, so we don't need to round out the album
@@ -109,15 +106,17 @@ class WidgetProvider : AppWidgetProvider() {
// Use RoundedCornersTransformation. This is because our hack to get a 1:1 aspect
// ratio on widget ImageViews doesn't actually result in a square ImageView, so
// clipToOutline won't work.
- val transform = RoundedCornersTransformation(
- context.getDimenSizeSafe(android.R.dimen.system_app_widget_inner_radius)
- .toFloat()
- )
+ val transform =
+ RoundedCornersTransformation(
+ context
+ .getDimenSizeSafe(android.R.dimen.system_app_widget_inner_radius)
+ .toFloat())
// The output of RoundedCornersTransformation is dimension-dependent, so scale up the
// image to the screen size to ensure consistent radii.
val metrics = context.resources.displayMetrics
- coverRequest.transformations(SquareFrameTransform(), transform)
+ coverRequest
+ .transformations(SquareFrameTransform(), transform)
.size(min(metrics.widthPixels, metrics.heightPixels))
} else {
coverRequest.transformations(SquareFrameTransform())
@@ -132,9 +131,8 @@ class WidgetProvider : AppWidgetProvider() {
fun reset(context: Context) {
logD("Resetting widget")
- AppWidgetManager.getInstance(context).updateAppWidget(
- ComponentName(context, this::class.java), createDefaultWidget(context)
- )
+ AppWidgetManager.getInstance(context)
+ .updateAppWidget(ComponentName(context, this::class.java), createDefaultWidget(context))
}
// --- OVERRIDES ---
@@ -170,8 +168,7 @@ class WidgetProvider : AppWidgetProvider() {
private fun requestUpdate(context: Context) {
logD("Sending update intent to PlaybackService")
- val intent = Intent(ACTION_WIDGET_UPDATE)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ val intent = Intent(ACTION_WIDGET_UPDATE).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
context.sendBroadcast(intent)
}
@@ -243,9 +240,8 @@ class WidgetProvider : AppWidgetProvider() {
// Default to the smallest view if no layout fits
logW("No good widget layout found")
- val minimum = requireNotNull(
- views.minByOrNull { it.key.width * it.key.height }?.value
- )
+ val minimum =
+ requireNotNull(views.minByOrNull { it.key.width * it.key.height }?.value)
updateAppWidget(id, minimum)
}
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetState.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetState.kt
index 5ef139531..64a0dbceb 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetState.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetState.kt
@@ -1,6 +1,5 @@
/*
* Copyright (c) 2021 Auxio Project
- * WidgetState.kt is part of Auxio.
*
* 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
@@ -15,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
+
package org.oxycblt.auxio.widgets
import android.graphics.Bitmap
diff --git a/build.gradle b/build.gradle
index 773a522aa..cc6d5fc5f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,6 +12,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
+ classpath "com.diffplug.spotless:spotless-plugin-gradle:6.3.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files