info: update documentation

Update the general documentation for the app.
This commit is contained in:
OxygenCobalt 2022-06-16 11:57:00 -06:00
parent 49e3c51bb6
commit 46a5cf293a
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 71 additions and 44 deletions

View file

@ -43,6 +43,7 @@ I primarily built Auxio for myself, but you can use it too, I guess.
- Opinionated UX that prioritizes ease of use over edge cases
- Customizable behavior
- Advanced media indexer that prioritizes correct metadata
- SD Card-aware folder management
- Reliable playback state persistence
- Full ReplayGain support (On MP3, MP4, FLAC, OGG, and OPUS)
- Edge-to-edge

View file

@ -176,7 +176,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
homeModel.updateCurrentSort(
unlikelyToBeNull(
homeModel
.getSortForDisplay(unlikelyToBeNull(homeModel.currentTab.value))
.getSortForDisplay(homeModel.currentTab.value)
.ascending(item.isChecked)))
}
else -> {
@ -185,7 +185,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
homeModel.updateCurrentSort(
unlikelyToBeNull(
homeModel
.getSortForDisplay(unlikelyToBeNull(homeModel.currentTab.value))
.getSortForDisplay(homeModel.currentTab.value)
.assignId(item.itemId)))
}
}

View file

@ -33,7 +33,6 @@ import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* A [HomeListFragment] for showing a list of [Album]s.
@ -54,7 +53,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
}
override fun getPopup(pos: Int): String? {
val album = unlikelyToBeNull(homeModel.albums.value)[pos]
val album = homeModel.albums.value[pos]
// Change how we display the popup depending on the mode.
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) {

View file

@ -33,7 +33,6 @@ import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* A [HomeListFragment] for showing a list of [Artist]s.
@ -54,7 +53,7 @@ class ArtistListFragment : HomeListFragment<Artist>() {
}
override fun getPopup(pos: Int): String? {
val artist = unlikelyToBeNull(homeModel.artists.value)[pos]
val artist = homeModel.artists.value[pos]
// Change how we display the popup depending on the mode.
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ARTISTS)) {

View file

@ -33,7 +33,6 @@ import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* A [HomeListFragment] for showing a list of [Genre]s.
@ -54,7 +53,7 @@ class GenreListFragment : HomeListFragment<Genre>() {
}
override fun getPopup(pos: Int): String? {
val genre = unlikelyToBeNull(homeModel.genres.value)[pos]
val genre = homeModel.genres.value[pos]
// Change how we display the popup depending on the mode.
return when (homeModel.getSortForDisplay(DisplayMode.SHOW_GENRES)) {

View file

@ -32,7 +32,6 @@ import org.oxycblt.auxio.ui.SyncBackingData
import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* A [HomeListFragment] for showing a list of [Song]s.
@ -53,7 +52,7 @@ class SongListFragment : HomeListFragment<Song>() {
}
override fun getPopup(pos: Int): String? {
val song = unlikelyToBeNull(homeModel.songs.value)[pos]
val song = homeModel.songs.value[pos]
// Change how we display the popup depending on the mode.
// Note: We don't use the more correct individual artist name here, as sorts are largely

View file

@ -35,7 +35,6 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* The ViewModel that provides a UI frontend for [PlaybackStateManager].
@ -194,15 +193,14 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
/** Remove a queue item using it's recyclerview adapter index. */
fun removeQueueDataItem(adapterIndex: Int) {
val index =
adapterIndex + (playbackManager.queue.size - unlikelyToBeNull(_nextUp.value).size)
val index = adapterIndex + (playbackManager.queue.size - _nextUp.value.size)
if (index in playbackManager.queue.indices) {
playbackManager.removeQueueItem(index)
}
}
/** Move queue items using their recyclerview adapter indices. */
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int): Boolean {
val delta = (playbackManager.queue.size - unlikelyToBeNull(_nextUp.value).size)
val delta = (playbackManager.queue.size - _nextUp.value.size)
val from = adapterFrom + delta
val to = adapterTo + delta
if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) {

View file

@ -33,7 +33,6 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* The component managing the [MediaSessionCompat] instance.
@ -122,8 +121,7 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
}
if (song.album.year != null) {
metadata.putString(
MediaMetadataCompat.METADATA_KEY_DATE, unlikelyToBeNull(song.album.year).toString())
metadata.putString(MediaMetadataCompat.METADATA_KEY_DATE, song.album.year.toString())
}
// Normally, android expects one to provide a URI to the metadata instance instead of

View file

@ -44,6 +44,8 @@ import org.oxycblt.auxio.util.logEOrThrow
* @author OxygenCobalt
*
* TODO: Make comparators static instances
*
* TODO: Separate sort mode and ascending state
*/
sealed class Sort(open val isAscending: Boolean) {
protected abstract val sortIntCode: Int

View file

@ -3,14 +3,17 @@ Auxio is a local music player with a fast, reliable UI/UX without the many usele
<b>Features</b>
- ExoPlayer based playback
- Customizable UI & Behavior
- Snappy UI derived from the latest Material Design guidelines
- Opinionated UX that prioritizes ease of use over edge cases
- Customizable behavior
- Advanced media indexer that prioritizes correct metadata
- SD Card-aware folder management
- Reliable playback state persistence
- ReplayGain support (On MP3, MP4, FLAC, OGG, and OPUS)
- Material You (Android 12+ only)
- Full ReplayGain support (On MP3, MP4, FLAC, OGG, and OPUS)
- Edge-to-edge
- Embedded covers support
- Search Functionality
- Audio/Headset focus
- Headset autoplay
- Stylish widgets that automatically adapt to their size
- Completely private and offline
- No rounded album covers (Unless you want them. Then you can.)
- No rounded album covers (Unless you want them. Then you can.)

View file

@ -5,12 +5,16 @@ This document is designed to provide an overview of Auxio's architecture and des
Auxio has a couple of core systems or concepts that should be understood when working with the codebase.
#### Package Structure
Auxio's package structure is strictly feature-oriented. For example, playback code is exclusively in the `playback` package,
and detail code is exclusively in the `detail` package. Sub-packages can be related to the code it contains, such as `detail.recycler`
for the detail UI adapters, or they can be related to a sub-feature, like `playback.queue` for the queue UI.
Auxio is deliberately structured in a way that I call "Anti-CLEAN Architecture". There is one gradle project, with sub-packages that
are strictly feature-oriented. For example, playback code is exclusively in the `playback` package, and detail code is exclusively in
the `detail` package. Sub-packages can be related to the code it contains, such as `detail.recycler` for the detail UI adapters, or
hey can be related to a sub-feature, like `playback.queue` for the queue UI.
The outliers here are `.ui` and `.util`, which are generic utility or component packages.
Sticking to a single project reduces compile times, makes it easier to add new features, and simply makes Auxio's code easier to
reason about.
A full run-down of Auxio's current package structure as of the latest version is shown below.
```
@ -23,7 +27,8 @@ org.oxycblt.auxio # Main UIs
│ └──.tabs # Home tab customization
├──.image # Image loading components
├──.music # Music data and loading
│ └──.excluded # Excluded Directories UI + Systems
│ ├──.backend # System-side music loading
│ └──.dirs # Music Folders UI + Systems
├──.playback # Playback UI + Systems
│ ├──.queue # Queue UI
│ ├──.replaygain # ReplayGain System + UIs
@ -31,7 +36,7 @@ org.oxycblt.auxio # Main UIs
│ └──.system # System-side playback [Services, ExoPlayer]
├──.search # Search UI
├──.settings # Settings UI + Systems
│ └──.pref # Int preference add-on
│ └──.ui # Preference extensions
├──.ui # Shared views and models
│ └──.accent # Color Scheme UI + Systems
├──.util # Shared utilities
@ -50,9 +55,12 @@ should be added as a new `Fragment` implementation and added to one of the two n
Fragments themselves are based off a super class called `ViewBindingFragment` that takes a view-binding and then
leverages it within the fragment lifecycle.
- Create variables [Bindings, Adapters, etc]
- Set up the UI
- Set up ViewModel instances and LiveData observers
Generally:
- Most variables are kept as member variables, and cleared out when the view is destroyed.
- Observing data is done through the `Fragment.launch` extension, and always points to another function
in order to reduce possible memory leaks.
- When possible (and readable), `Fragment` implementations inherit any listener interfaces they need,
and simply clear them out when done.
`findViewById` is to **only** be used when interfacing with non-Auxio views. Otherwise, view-binding should be
used in all cases. Code that involves retrieving the binding should be isolated into its own function, with
@ -72,7 +80,7 @@ Auxio's codebase is mostly centered around 4 different types of code that commun
- UIs: Fragments, RecyclerView items, and Activities are part of this class. All of them should have little data logic
in them and should primarily focus on displaying information in their UIs.
- ViewModels: These usually contain data and values that a UI can display, along with doing data processing. The data
often takes the form of `MutableLiveData` or `LiveData`, which can be observed.
often takes the form of `MutableStateFlow` or `StateFlow`, which can be observed.
- Shared Objects: These are the fundamental building blocks of Auxio, and exist at the process level. These are usually
retrieved using `getInstance` or a similar function. Shared Objects should be avoided in UIs, as their volatility can
cause problems. Its better to use a ViewModel and their exposed data instead.
@ -172,9 +180,6 @@ with a data list similar to this:
Each adapter instance also handles the highlighting of the currently playing item in the detail menu.
`DetailViewModel` acts as the holder for the currently displaying items, along with having the `navToItem` LiveData that coordinates menu/playback
navigation [Such as when a user presses "Go to artist"]
#### `.home`
This package contains the components for the "home" UI in Auxio, or the UI that the user first sees when they open the app.
@ -192,17 +197,42 @@ This should not be used for UIs.
- `BaseFetcher`, which is effectively Auxio's image loading routine. Most changes to image loading should be done there, and not it's
sub-classes like `AlbumArtFetcher`.
#### `.music`
This package contains all `Music` implementations, the music loading implementation, and the excluded directory system.
This package also contains the two UI components used for all covers in Auxio:
- `StyledImageView`, which adds extensions for dynamically loading covers, handles rounded corners, and a stable icon style.
- `ImageGroup`, an extension of `StyledImageView` that all of the previous features, alongside a playing indicator and one custom view.
Key classes in this package include:
- `MusicStore`, which is the primary access point for music data.
- `Indexer`, which implements all of the `MediaStore` hacks to create a good metadata indexer for Auxio.
#### `.music`
This package contains all `Music` implementations, the music loading implementation, and the music folder system. This is the second
most complicated package in the app, as loading music in a sane way is horribly difficult.
The major classes are:
- `MusicStore`, which is the container for a `Library` instance. Any code wanting to access the library should use this
- `Indexer`, which manages how music is loaded. This is only used by code that must reflect the music loading state.
Internally, there are several other major systems:
- `IndexerService`, which does the indexer work in the background.
- `Indexer.Backend` implementations, which actually talk to the media database and load music. As it stands, there
are two classes of backend:
- Version-specific `MediaStoreBackend` implementations, which transform the (often insane) music data from Android
into a usable `Song`.
- `ExoPlayerBackend`, which mutates audio with extracted ID3v2 and Vorbis tags. This enables some extra features
and side-steps unfixable issues with `MediaStore`
- `StorageFramework`, which is a group of utilities that allows Auxio to be volume-aware and to work with both
extension-based and format-based mime types.
The music loading process is roughly as follows:
1. Something triggers `IndexerService` to start indexing, either by the UI or by the service itself starting.
2. `Indexer` picks an appropriate `Backend`, and begins loading music. `Indexer` may periodically update it's state
during this time with the current progress.
3. In the case that `IndexerService` is killed, `Indexer` falls back to a previous state (or null if there isn't one)
4. If the music loading process completes, `Indexer` will push a `Response`. `IndexerService` will read this, and in
the case that the new `Library` differs, it will push it to `MusicStore`
5. `MusicStore` updates any `Callback` instances with the new `Library`.
#### `.playback`
This module not only contains the playback system described above, but also multiple other components:
- `queue` contains the Queue UI and it's fancy item UIs.
- `queue` contains the Queue UI and it's fancy item system.
- `replaygain` contains the ReplayGain implementation and the UIs related to it. Auxio's ReplayGain implementation is
somewhat different compared to other apps, as it leverages ExoPlayer's metadata and audio processing systems to not only
parse ReplayGain tags, but also allow volume amplification above 100%.
@ -228,14 +258,13 @@ a normal choice preference to be backed by the integer representations that Auxi
#### `.ui`
Shared views and view configuration models. This contains:
- Customized views such as `EdgeAppBarLayout`, `StyledImageView`, and others, which provide extra styling and behavior
not provided by default.
- Important `Fragment` superclasses like `ViewBindingFragment` and `MenuFragment`
- Customized views such as `EdgeAppBarLayout`, and others, which just fix existing shortcomings with the views.
- Configuration models like `DisplayMode` and `Sort`, which are used in many places but aren't tied to a specific feature.
- `newMenu` and `ActionMenu`, which automates menu creation for most data types.
- The `RecyclerView` adapter framework described previously.
- The view-binding super classes described previously.
- `BottomSheetLayout`, a highly important layout that underpins Auxio's UI flow.
- Standard `ViewHolder` implementations that can be used for common datatypes.
- `NavigationViewModel`, which acts as an interface to control navigation to a particular item and navigation within `MainFragment`
#### `.util`
Shared utilities. This is primarily for QoL when developing Auxio. Documentation is provided on each method.