info: update architecture

Update architecture to reflect the new changes made in 2.3.0.
This commit is contained in:
OxygenCobalt 2022-05-26 10:03:41 -06:00
parent 1a02aaad4b
commit b59ef1a746
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 107 additions and 102 deletions

View file

@ -21,9 +21,9 @@ import android.app.Application
import coil.ImageLoader import coil.ImageLoader
import coil.ImageLoaderFactory import coil.ImageLoaderFactory
import coil.request.CachePolicy import coil.request.CachePolicy
import org.oxycblt.auxio.image.AlbumArtFetcher import org.oxycblt.auxio.image.AlbumCoverFetcher
import org.oxycblt.auxio.image.ArtistImageFetcher import org.oxycblt.auxio.image.ArtistImageFetcher
import org.oxycblt.auxio.image.CrossfadeFactory import org.oxycblt.auxio.image.CrossfadeTransitionFactory
import org.oxycblt.auxio.image.GenreImageFetcher import org.oxycblt.auxio.image.GenreImageFetcher
import org.oxycblt.auxio.image.MusicKeyer import org.oxycblt.auxio.image.MusicKeyer
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
@ -41,13 +41,13 @@ class AuxioApp : Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader { override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(applicationContext) return ImageLoader.Builder(applicationContext)
.components { .components {
add(AlbumArtFetcher.SongFactory()) add(AlbumCoverFetcher.SongFactory())
add(AlbumArtFetcher.AlbumFactory()) add(AlbumCoverFetcher.AlbumFactory())
add(ArtistImageFetcher.Factory()) add(ArtistImageFetcher.Factory())
add(GenreImageFetcher.Factory()) add(GenreImageFetcher.Factory())
add(MusicKeyer()) add(MusicKeyer())
} }
.transitionFactory(CrossfadeFactory()) .transitionFactory(CrossfadeTransitionFactory())
.diskCachePolicy(CachePolicy.DISABLED) // Not downloading anything, so no disk-caching .diskCachePolicy(CachePolicy.DISABLED) // Not downloading anything, so no disk-caching
.build() .build()
} }

View file

@ -50,10 +50,10 @@ class MusicKeyer : Keyer<Music> {
} }
/** /**
* Fetcher that returns the album art for a given [Album] or [Song], depending on the factory used. * Fetcher that returns the album cover for a given [Album] or [Song], depending on the factory used.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class AlbumArtFetcher private constructor(private val context: Context, private val album: Album) : class AlbumCoverFetcher private constructor(private val context: Context, private val album: Album) :
BaseFetcher() { BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
return fetchArt(context, album)?.let { stream -> return fetchArt(context, album)?.let { stream ->
@ -66,13 +66,13 @@ class AlbumArtFetcher private constructor(private val context: Context, private
class SongFactory : Fetcher.Factory<Song> { class SongFactory : Fetcher.Factory<Song> {
override fun create(data: Song, options: Options, imageLoader: ImageLoader): Fetcher { override fun create(data: Song, options: Options, imageLoader: ImageLoader): Fetcher {
return AlbumArtFetcher(options.context, data.album) return AlbumCoverFetcher(options.context, data.album)
} }
} }
class AlbumFactory : Fetcher.Factory<Album> { class AlbumFactory : Fetcher.Factory<Album> {
override fun create(data: Album, options: Options, imageLoader: ImageLoader) = override fun create(data: Album, options: Options, imageLoader: ImageLoader) =
AlbumArtFetcher(options.context, data) AlbumCoverFetcher(options.context, data)
} }
} }
@ -133,11 +133,11 @@ private inline fun <T : Any, R : Any> Collection<T>.mapAtMost(
val out = mutableListOf<R>() val out = mutableListOf<R>()
for (item in this) { for (item in this) {
if (out.size >= until) { if (out.size < until) {
transform(item)?.let(out::add)
} else {
break break
} }
transform(item)?.let(out::add)
} }
return out return out

View file

@ -30,7 +30,7 @@ import coil.transition.TransitionTarget
* Like they used to. * Like they used to.
* @author Coil Team * @author Coil Team
*/ */
class CrossfadeFactory : Transition.Factory { class CrossfadeTransitionFactory : Transition.Factory {
override fun create(target: TransitionTarget, result: ImageResult): Transition { override fun create(target: TransitionTarget, result: ImageResult): Transition {
// Don't animate if the request was fulfilled by the memory cache. // Don't animate if the request was fulfilled by the memory cache.
if (result is SuccessResult && result.dataSource == DataSource.MEMORY_CACHE) { if (result is SuccessResult && result.dataSource == DataSource.MEMORY_CACHE) {

View file

@ -29,7 +29,7 @@ enum class RepeatMode {
ALL, ALL,
TRACK; TRACK;
/** Increment the LoopMode, e.g from [NONE] to [ALL] */ /** Increment the mode, e.g from [NONE] to [ALL] */
fun increment(): RepeatMode { fun increment(): RepeatMode {
return when (this) { return when (this) {
NONE -> ALL NONE -> ALL
@ -47,8 +47,7 @@ enum class RepeatMode {
} }
/** /**
* Convert the LoopMode to an int constant that is saved in PlaybackStateDatabase * The integer code representing this particular mode.
* @return The int constant for this mode
*/ */
val intCode: Int val intCode: Int
get() = get() =

View file

@ -27,9 +27,7 @@ import org.oxycblt.auxio.BuildConfig
* Shortcut method for logging a non-string [obj] to debug. Should only be used for debug * Shortcut method for logging a non-string [obj] to debug. Should only be used for debug
* preferably. * preferably.
*/ */
fun Any.logD(obj: Any) { fun Any.logD(obj: Any?) = logD("$obj")
logD(obj.toString())
}
/** /**
* Shortcut method for logging [msg] to the debug console. Handles debug builds and anonymous * Shortcut method for logging [msg] to the debug console. Handles debug builds and anonymous
@ -38,19 +36,15 @@ fun Any.logD(obj: Any) {
fun Any.logD(msg: String) { fun Any.logD(msg: String) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
basedCopyleftNotice() basedCopyleftNotice()
Log.d(name, msg) Log.d(autoTag, msg)
} }
} }
/** 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) { fun Any.logW(msg: String) = Log.w(autoTag, msg)
Log.w(name, 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) { fun Any.logE(msg: String) = Log.e(autoTag, msg)
Log.e(name, msg)
}
/** /**
* Logs an error in production while still throwing it in debug mode. This is useful for * Logs an error in production while still throwing it in debug mode. This is useful for
@ -65,10 +59,9 @@ fun Throwable.logTraceOrThrow() {
} }
/** /**
* Get a non-nullable name, used so that logs will always show up by Auxio * Automatically creates a tag that identifies the object currently logging.
* @return The name of the object, otherwise "Anonymous Object"
*/ */
private val Any.name: String private val Any.autoTag: String
get() = "Auxio.${this::class.simpleName ?: "Anonymous Object"}" get() = "Auxio.${this::class.simpleName ?: "Anonymous Object"}"
/** /**

View file

@ -76,6 +76,7 @@
<string name="set_headset_autoplay">Kopfhörer automatische Wiedergabe</string> <string name="set_headset_autoplay">Kopfhörer automatische Wiedergabe</string>
<string name="set_headset_autoplay_desc">Beginne die Wiedergabe immer, wenn Kopfhörer verbunden sind (funktioniert nicht auf allen Geräten)</string> <string name="set_headset_autoplay_desc">Beginne die Wiedergabe immer, wenn Kopfhörer verbunden sind (funktioniert nicht auf allen Geräten)</string>
<string name="set_replay_gain">ReplayGain</string> <string name="set_replay_gain">ReplayGain</string>
<string name="set_pre_amp">ReplayGain prälautverstärkung</string>
<string name="set_pre_amp_desc">Während Musikwiedergabe, Die Prälautverstärkung trifft zu dem aktuellem Abgleich</string> <string name="set_pre_amp_desc">Während Musikwiedergabe, Die Prälautverstärkung trifft zu dem aktuellem Abgleich</string>
<string name="set_pre_amp_with">Abgleich mit Metadaten</string> <string name="set_pre_amp_with">Abgleich mit Metadaten</string>
<string name="set_pre_amp_without">Abgleich ohne Metadaten</string> <string name="set_pre_amp_without">Abgleich ohne Metadaten</string>

View file

@ -9,7 +9,7 @@ buildscript {
} }
dependencies { dependencies {
classpath "com.android.tools.build:gradle:7.2.0" classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.3.0" classpath "com.diffplug.spotless:spotless-plugin-gradle:6.3.0"

View file

@ -9,28 +9,31 @@ Auxio's package structure is strictly feature-oriented. For example, playback co
and detail code is exclusively in the `detail` package. Sub-packages can be related to the code it contains, such as `detail.recycler` 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. for the detail UI adapters, or they 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.
A full run-down of Auxio's current package structure as of the latest version is shown below. A full run-down of Auxio's current package structure as of the latest version is shown below.
``` ```
org.oxycblt.auxio # Main UIs org.oxycblt.auxio # Main UIs
├──.accent # Color Scheme UI + Systems
├──.coil # Image loading components
├──.detail # Album/Artist/Genre detail UIs ├──.detail # Album/Artist/Genre detail UIs
│ └──.recycler # RecyclerView components for detail UIs │ └──.recycler # RecyclerView components for detail UIs
├──.home # Home UI ├──.home # Home UI
│ ├──.fastscroll # Fast scroller UI │ ├──.fastscroll # Fast scroller UI
│ ├──.list # Home item lists │ ├──.list # Home item lists
│ └──.tabs # Home tab customization │ └──.tabs # Home tab customization
├──.image # Image loading components
├──.music # Music data and loading ├──.music # Music data and loading
│ └──.excluded # Excluded Directories UI + Systems │ └──.excluded # Excluded Directories UI + Systems
├──.playback # Playback UI + Systems ├──.playback # Playback UI + Systems
│ ├──.queue # Queue UI │ ├──.queue # Queue UI
│ ├──.replaygain # ReplayGain System + UIs
│ ├──.state # Playback state backend │ ├──.state # Playback state backend
│ └──.system # System-side playback [Services, ExoPlayer] │ └──.system # System-side playback [Services, ExoPlayer]
├──.search # Search UI ├──.search # Search UI
├──.settings # Settings UI + Systems ├──.settings # Settings UI + Systems
│ └──.pref # Int preference add-on │ └──.pref # Int preference add-on
├──.ui # Shared views and models ├──.ui # Shared views and models
│ └──.accent # Color Scheme UI + Systems
├──.util # Shared utilities ├──.util # Shared utilities
└──.widgets # AppWidgets └──.widgets # AppWidgets
``` ```
@ -73,8 +76,8 @@ often takes the form of `MutableLiveData` or `LiveData`, which can be observed.
- Shared Objects: These are the fundamental building blocks of Auxio, and exist at the process level. These are usually - 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 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. cause problems. Its better to use a ViewModel and their exposed data instead.
- Utilities: These are largely found in the `.util` and `.coil` packages, taking the form of standalone or extension - Utilities: These are largely found in the `.util` package, taking the form of standalone or extension functions
functions that can be used anywhere. that can be used anywhere.
Ideally, UIs should only be talking to ViewModels, ViewModels should only be talking to the Shared Objects, and Shared Objects Ideally, UIs should only be talking to ViewModels, ViewModels should only be talking to the Shared Objects, and Shared Objects
should only be talking to other shared objects. All objects can use the utility functions where appropriate. should only be talking to other shared objects. All objects can use the utility functions where appropriate.
@ -85,13 +88,14 @@ Auxio represents data in multiple ways.
`Item` is the base class for most music and UI data in Auxio, with a single ID field meant to mark it as unique. `Item` is the base class for most music and UI data in Auxio, with a single ID field meant to mark it as unique.
It has the following implementations: It has the following implementations:
- `Music` is a `Item` that represents music. It adds a `name` field that represents the raw name of the music (from `MediaStore`). - `Music` is a `Item` that represents music. It adds a `name` field that represents the raw name of the music (from `MediaStore`),
- `MusicParent` is a type of `Music` that contains children. It adds a `resolveName` field that converts the raw `MediaStore` name and a `resolveName` method meant to resolve the name in context of the UI.
to a name that can be used in UIs. - `MusicParent` is a type of `Music` that contains children.
- `Header` corresponds to a simple header. The Detail UIs have a derivative called `SortHeader` that also adds a sorting button. - `Header` corresponds to a simple header with a title and no interaction functionality. There are also the detail-specific
`DiscHeader` and `SortHeader`, however these are largely unrelated to `Header`.
Other data types represent a specific UI configuration or state: Other data types represent a specific UI configuration or state:
- Sealed classes like `Sort` contain data with them that can be modified. - Sealed classes like `Sort` contain an ascending state that can be modified immutably.
- Enums like `DisplayMode` and `RepeatMode` only contain static data, such as a string resource. - Enums like `DisplayMode` and `RepeatMode` only contain static data, such as a string resource.
Things to keep in mind while working with music data: Things to keep in mind while working with music data:
@ -107,10 +111,11 @@ objects.
a song, as it will always show collaborator information first before defaulting to the album artist. a song, as it will always show collaborator information first before defaulting to the album artist.
#### Music Access #### Music Access
All music on a system is asynchronously loaded into the shared object `MusicStore`. Because of this, **`MusicStore` may not be available at all times**. All music on a system is asynchronously loaded into the shared object `MusicStore`. More specifically, it is accessible within
the `Library` construct. By the nature of music loading, **`Library` may not be available at all times.**
- ViewModels should try to await or gracefully exit the called method if `MusicStore` is not available - ViewModels should try to await or gracefully exit the called method if `Library` is not available
- In the case that a ViewModel needs a `MusicStore` instance to function, an instance can be required. This should be done sparingly. - In the case that a ViewModel needs a `Library` instance to function, it can be asserted with `requireNotNull`. This should be done sparingly.
- Other shared objects that rely on `MusicStore` [like `PlaybackStateManager`] will no-op if music is not available. - Other shared objects that rely on `MusicStore` [like `PlaybackStateManager`] will no-op if music is not available.
If the loading status needs to be shown in a UI, `MusicViewModel` can be used to observe the current music loader response. If the loading status needs to be shown in a UI, `MusicViewModel` can be used to observe the current music loader response.
@ -120,17 +125,16 @@ Auxio's playback system is somewhat unorthodox, as it avoids much of the android
The diagram below highlights the overall structure and connections: The diagram below highlights the overall structure and connections:
``` ```
[Requests update from] ┌──────────────────── PlaybackService ────────────────┐
┌─────────────────────────────────────────────────────────────────────────────────┐ │ │ │
│ │ PlaybackStateManager [Communicates with] │ │
┌──────────────────── PlaybackService ──────────────────── WidgetController ──────────────────── WidgetProvider ┘ │ │ [Contains] │
│ │ [Contains] [Controls] │ │ │
PlaybackStateManager [Communicates with] │ │ ├ WidgetComponent ┤
│ │ [Contains] │ ├ NotificationComponent ┤
│ │ │ ├ MediaSessionComponent ┤
│ ├ Notification │ └ Player ┘
│ ├ MediaSession
│ └ Player
└──────────────────── PlaybackViewModel ───────────────────── UIs └──────────────────── PlaybackViewModel ───────────────────── UIs
[Communicates With] [Communicates With]
@ -141,14 +145,13 @@ PlaybackStateManager [Communicates with] │
is also prone to memory leaks if not cleared when done. `PlaybackViewModel` should be used instead, as it exposes stable data and safe functions is also prone to memory leaks if not cleared when done. `PlaybackViewModel` should be used instead, as it exposes stable data and safe functions
that UIs can use to interact with the playback state. that UIs can use to interact with the playback state.
`PlaybackService`'s job is to use the playback state to manage the ExoPlayer instance, the notification, the widget, and also modify the state depending on `PlaybackService`'s job is to use the playback state to manage the ExoPlayer instance, the notification, the media session, the widget, and
system events, such as when a button is pressed on a headset. It should **never** be bound to, mostly because there is no need given that also modify the state depending on system events, such as when a button is pressed on a headset. It should **never** be bound to, mostly because
`PlaybackViewModel` exposes the same data in a much safer fashion. there is no need given that `PlaybackViewModel` exposes the same data in a much safer fashion.
#### Data Integers #### Data Integers
Integer representations of data/UI elements are used heavily in Auxio, primarily for efficiency. Integer representations of data/UI elements are used heavily in Auxio, primarily for efficiency. To prevent any strange bugs, all integer
To prevent any strange bugs, all integer representations must be unique. To see a table of all current integers, see the `C` class within representations must be unique. To see a table of all current integers, see the `C` class within the project.
the project.
Some datatypes [like `Tab` and `Sort`] have even more fine-grained integer representations for other data. More information can be found in Some datatypes [like `Tab` and `Sort`] have even more fine-grained integer representations for other data. More information can be found in
the documentation for those datatypes. the documentation for those datatypes.
@ -159,24 +162,6 @@ the documentation for those datatypes.
This is the root package and contains the application instance and the landing UIs. This should be kept sparse with most other code being placed This is the root package and contains the application instance and the landing UIs. This should be kept sparse with most other code being placed
into a package. into a package.
#### `.accent`
This package is responsible for Auxio's color schemes, internally known as accents due to legacy code.
It contains an object that represents the attributes of an accent, but this should be avoided in favor of
resolving color attributes directly, such as `colorPrimary`. This package also contains the UIs for picking
an accent.
#### `.coil`
[Coil](https://github.com/coil-kt/coil) is the image loader used by Auxio. All image loading is done through these four functions/binding adapters:
- `app:albumArt`: Binding Adapter that will load the cover art for a song or album
- `app:artistImage`: Binding Adapter that will load the artist image
- `app:genreImage`: Binding Adapter that will load the genre image
- `loadBitmap`: Function that will take a song and return a bitmap, this should not be used in anything UI related, that is what the binding adapters above are for.
This should be enough to cover most use cases in Auxio.
Internally, multiple fetchers are provided to transform `Music` instances into images. All of these fetchers inherit `BaseFetcher`, which implements
the necessary methods for loading album artwork and creating the mosaics shown in artist/genre images.
#### `.detail` #### `.detail`
Contains all the detail UIs for some data types in Auxio. All detail user interfaces share the same base layout (A Single RecyclerView) and Contains all the detail UIs for some data types in Auxio. All detail user interfaces share the same base layout (A Single RecyclerView) and
@ -190,13 +175,6 @@ Each adapter instance also handles the highlighting of the currently playing ite
`DetailViewModel` acts as the holder for the currently displaying items, along with having the `navToItem` LiveData that coordinates menu/playback `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"] navigation [Such as when a user presses "Go to artist"]
#### `.excluded`
This package is responsible for the excluded directory system. It contains the database of excluded directories and the dialog that appears when
editing them.
**Note:** Certain naming in this package might not line up with the current name of the package. This is because updating those names would break
compatibility with previous versions of Auxio.
#### `.home` #### `.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. This package contains the components for the "home" UI in Auxio, or the UI that the user first sees when they open the app.
@ -205,19 +183,35 @@ This package contains the components for the "home" UI in Auxio, or the UI that
- The `list` package contains the individual fragments for each list of music. These are all placed in the top-level ViewPager instance. - The `list` package contains the individual fragments for each list of music. These are all placed in the top-level ViewPager instance.
- The `tabs` package contains the data representation of an individual library tab and the UIs for editing them. - The `tabs` package contains the data representation of an individual library tab and the UIs for editing them.
#### `.image`
[Coil](https://github.com/coil-kt/coil) is the image loader used by Auxio. This package contains the components Auxio leverages to load images
in a stable manner. Usually, you do not need to import this package elsewhere, but there are some important components:
- `BitmapProvider`, which allows external components (Such as in PlaybackService) to load a `Bitmap` in a way not prone to race conditions.
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` #### `.music`
This package contains all `BaseModel` implementations and the music loading implementation. This also includes `Header`/`ActionHeader`, as those This package contains all `Music` implementations, the music loading implementation, and the excluded directory system.
data objects have to inherit `BaseModel` so that they can be placed alongside `Music` instances in `RecyclerView` instances.
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.
#### `.playback` #### `.playback`
This module not only contains the playback system described above, but also multiple other components: 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 transitions - `queue` contains the Queue UI and it's fancy item UIs.
- `state` contains the core playback state and persistence system - `state` contains the core playback state and persistence system.
- `system` contains the system-facing playback 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
- `system` contains the system-facing playback system, i.e `PlaybackService`
The most important part of this module is `PlaybackLayout`, which is a custom `ViewGroup` that implements the playback bar and it's ability to The base package contains the user-facing UIs representing the playback state, specifically the playback bar and the
slide up into the full playback view. `MainFragment` controls this `ViewGroup`. playback panel that it expands into. Note that while the playback UI does rely on `BottomSheetLayout`, the layout is
designed to be at least somewhat re-usable, so it is in the generic `.ui` class.
#### `.search` #### `.search`
Package for Auxio's search functionality, `SearchViewHolder` handles the data results and filtering while `SearchFragment`/`SearchAdapter` handles the Package for Auxio's search functionality, `SearchViewHolder` handles the data results and filtering while `SearchFragment`/`SearchAdapter` handles the
@ -225,27 +219,45 @@ display of the results and user input.
#### `.settings` #### `.settings`
The settings system is primarily based off of `SettingsManager`, a wrapper around `SharedPreferences`. This allows settings to be read/written in a The settings system is primarily based off of `SettingsManager`, a wrapper around `SharedPreferences`. This allows settings to be read/written in a
much simpler/safer manner and without a context being needed. The Settings UI is largely contained in `SettingsListFragment`, while the `pref` much simpler/safer manner and without a context being needed. The Settings UI is largely contained in `SettingsListFragment`, which is a standard
sub-package contains `IntListPreference`, which allows Auxio's integer representations to be used with the preference UI. The about dialog `PreferenceFragment` implementation wrapped by the more general `SettingsFragment`.
also resides in this package.
Internally, the settings package also leverages a couple custom preference implementations, notably `IntListPreference`, which enables
a normal choice preference to be backed by the integer representations that Auxio uses.
#### `.ui` #### `.ui`
Shared views and view configuration models. This contains: Shared views and view configuration models. This contains:
- Customized views such as `EdgeAppBarLayout` and `EdgeRecyclerView`, which add some extra functionality not provided by default - Customized views such as `EdgeAppBarLayout`, `StyledImageView`, and others, which provide extra styling and behavior
not provided by default.
- Configuration models like `DisplayMode` and `Sort`, which are used in many places but aren't tied to a specific feature. - 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 - `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.
#### `.util` #### `.util`
Shared utilities. This is primarily for QoL when developing Auxio. Documentation is provided on each method. Shared utilities. This is primarily for QoL when developing Auxio. Documentation is provided on each method.
Utilities are separated into a few groups:
- Context utilties are extensions of `Context` and generally act as shortcuts for that class.
- Framework utilities extend a variety of view implementations to add new behavior or shortcuts.
- Primitive utilities operate on basic datatypes and are mostly shortcuts.
- Log utilities are a more light-weight logging framework that Auxio leverages instead of
bloated and over-engineered libraries like Timber.
#### `.widgets` #### `.widgets`
This package contains Auxio's AppWidget implementation, which deviates from other AppWidget implementations by packing multiple This package contains Auxio's AppWidget implementation, which deviates from other AppWidget implementations by packing multiple
different layouts into a single widget and then switching between them depending on the widget size. different layouts into a single widget and then switching between them depending on the widget size. Note that since `RemoteViews`
and the AppWidget API in general is incredibly outdated and limited, this package deviates from much of Auxio's normal UI
conventions.
When `WidgetProvider` creates a layout, it first turns the `PlaybackStateManager` instance into a `WidgetState`, which is The playback service owns `WidgetComponent`, which listens to `PlaybackStateManager` for updates. During an update, it reloads
an immutable version of the playback state that negates some of the problems with using a shared object here. It then picks all song metadata and playback state into a `WidgetState`, which is an immutable version of the playback state that negates some
a layout [e.g "Form"] depending on its current dimensions and applies the `WidgetState` object to that. of the problems with using a volatile shared object.
**Note:** The AppWidget implementation violates UI conventions by directly interfacing with coil and `PlaybackStateManager`. `WidgetProvider` is the widget "implementation" exposed in the manifest. When `WidgetComponent` updates it, the class will create
This is required due to `RemoteView` limitations. a series of layouts [e.g "Forms"] for a variety of "size buckets" that would adequately contain the widget. This is then used as
the widget views, either with the native responsive behavior on Android 12 and above, or with the responsive behavior backported
to older devices.