all: refactor utils

Do the final utility refactor, placing custom views into a .ui
submodule and the general utils into a new .util module. This
system seems to stick well.
This commit is contained in:
OxygenCobalt 2021-08-23 17:05:42 -06:00
parent 9aa2c99be4
commit 5fe01777d0
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
69 changed files with 476 additions and 483 deletions

View file

@ -32,6 +32,9 @@ import org.oxycblt.auxio.databinding.ActivityMainBinding
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.system.PlaybackService
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.isEdgeOn
import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD
/**
* The single [AppCompatActivity] for Auxio.

View file

@ -27,6 +27,8 @@ import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.isLandscape
import org.oxycblt.auxio.util.logD
/**
* A wrapper around the home fragment that shows the playback fragment and controls

View file

@ -27,7 +27,7 @@ import androidx.annotation.StringRes
import androidx.annotation.StyleRes
import androidx.core.text.HtmlCompat
import org.oxycblt.auxio.R
import org.oxycblt.auxio.resolveStateList
import org.oxycblt.auxio.util.resolveStateList
/**
* A list of all possible accents.

View file

@ -23,8 +23,8 @@ import androidx.appcompat.widget.TooltipCompat
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemAccentBinding
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.resolveStateList
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.resolveStateList
/**
* An adapter that displays the list of all possible accents, and highlights the current one.

View file

@ -26,10 +26,10 @@ import androidx.appcompat.app.AlertDialog
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogAccentBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.resolveColor
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.LifecycleDialog
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.resolveColor
/**
* Dialog responsible for showing the list of accents to select.
@ -100,7 +100,7 @@ class AccentDialog : LifecycleDialog() {
}
companion object {
const val TAG = "TAG_ACCENT_DIALOG"
const val TAG = BuildConfig.APPLICATION_ID + ".tag.ACCENT_PICKER"
const val KEY_PENDING_ACCENT = BuildConfig.APPLICATION_ID + ".key.PENDING_ACCENT"
}
}

View file

@ -27,18 +27,18 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearSmoothScroller
import org.oxycblt.auxio.R
import org.oxycblt.auxio.canScroll
import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.showToast
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.canScroll
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
/**
* The [DetailFragment] for an album.

View file

@ -26,7 +26,6 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.adapters.ArtistDetailAdapter
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
@ -34,9 +33,10 @@ import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.SortMode
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.logD
/**
* The [DetailFragment] for an artist.

View file

@ -28,9 +28,9 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.isLandscape
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.isLandscape
/**
* A Base [Fragment] implementing the base features shared across all detail fragments.

View file

@ -25,8 +25,8 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.SortMode
/**
* ViewModel that stores data for the [DetailFragment]s, such as what they're showing & what

View file

@ -25,7 +25,6 @@ import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.detail.adapters.GenreDetailAdapter
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
@ -34,6 +33,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.logD
/**
* The [DetailFragment] for a genre.

View file

@ -27,15 +27,15 @@ import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.ItemAlbumHeaderBinding
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.disable
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.setTextColorResource
import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.util.disable
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.setTextColorResource
/**
* An adapter for displaying the details and [Song]s of an [Album]

View file

@ -28,18 +28,18 @@ import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding
import org.oxycblt.auxio.databinding.ItemArtistSongBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.disable
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.setTextColorResource
import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.util.disable
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.setTextColorResource
/**
* An adapter for displaying the [Album]s and [Song]s of an artist.

View file

@ -27,15 +27,15 @@ import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.ItemGenreHeaderBinding
import org.oxycblt.auxio.databinding.ItemGenreSongBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.disable
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.setTextColorResource
import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.util.disable
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.setTextColorResource
/**
* An adapter for displaying the [Song]s of a genre.

View file

@ -23,9 +23,9 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.database.sqlite.transaction
import org.oxycblt.auxio.assertBackgroundThread
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.queryAll
import org.oxycblt.auxio.util.assertBackgroundThread
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.queryAll
/**
* Database for storing excluded directories.

View file

@ -31,13 +31,14 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.MainActivity
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogExcludedBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.showToast
import org.oxycblt.auxio.ui.LifecycleDialog
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
import kotlin.system.exitProcess
/**
@ -177,6 +178,6 @@ class ExcludedDialog : LifecycleDialog() {
}
companion object {
const val TAG = "TAG_BLACKLIST_DIALOG"
const val TAG = BuildConfig.APPLICATION_ID + ".tag.EXCLUDED"
}
}

View file

@ -22,7 +22,7 @@ import android.annotation.SuppressLint
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemExcludedDirBinding
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.util.inflater
/**
* Adapter that shows the blacklist entries and their "Clear" button.

View file

@ -27,10 +27,10 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.AlbumViewHolder
import org.oxycblt.auxio.recycler.ArtistViewHolder
import org.oxycblt.auxio.recycler.GenreViewHolder
import org.oxycblt.auxio.recycler.SongViewHolder
import org.oxycblt.auxio.ui.AlbumViewHolder
import org.oxycblt.auxio.ui.ArtistViewHolder
import org.oxycblt.auxio.ui.GenreViewHolder
import org.oxycblt.auxio.ui.SongViewHolder
class HomeAdapter(
private val doOnClick: (data: BaseModel) -> Unit,

View file

@ -33,14 +33,14 @@ import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
/**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail

View file

@ -25,19 +25,18 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.recycler.sliceArticle
import org.oxycblt.auxio.spans
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.logD
/**
* Fragment that contains a list of items specified by a [DisplayMode]. This fragment
@ -83,10 +82,7 @@ class HomeListFragment : Fragment() {
binding.homeRecycler.apply {
adapter = homeAdapter
setHasFixedSize(true)
if (spans != 1) {
layoutManager = GridLayoutManager(requireContext(), spans)
}
applySpans()
}
// --- VIEWMODEL SETUP ---

View file

@ -26,7 +26,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.ui.DisplayMode
class HomeViewModel : ViewModel() {
private val mGenres = MutableLiveData(listOf<Genre>())

View file

@ -31,8 +31,8 @@ import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLoadingBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.util.logD
/**
* Fragment that handles what to display during the loading process.

View file

@ -28,7 +28,7 @@ import android.provider.MediaStore.Audio.Media
import androidx.core.database.getStringOrNull
import org.oxycblt.auxio.R
import org.oxycblt.auxio.excluded.ExcludedDatabase
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.util.logD
/**
* Class that loads/constructs [Genre]s, [Artist]s, [Album]s, and [Song] objects from the filesystem

View file

@ -25,8 +25,8 @@ import android.net.Uri
import android.provider.OpenableColumns
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
import java.lang.Exception
/**

View file

@ -27,7 +27,7 @@ import android.widget.TextView
import androidx.core.text.isDigitsOnly
import androidx.databinding.BindingAdapter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.getPlural
import org.oxycblt.auxio.util.getPlural
/**
* A complete array of all the hardcoded genre values for ID3 <v3, contains standard genres and

View file

@ -28,7 +28,7 @@ import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.util.logD
/**
* A [Fragment] that displays the currently played song at a glance, with some basic controls.
@ -36,6 +36,7 @@ import org.oxycblt.auxio.logD
*
* Instantiation is done by FragmentContainerView, **do not instantiate this fragment manually.**
* @author OxygenCobalt
* TODO: Add more controls to this view depending on screen width
*/
class CompactPlaybackFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels()

View file

@ -31,11 +31,11 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.databinding.FragmentPlaybackBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.resolveDrawable
import org.oxycblt.auxio.resolveStateList
import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.resolveDrawable
import org.oxycblt.auxio.util.resolveStateList
/**
* A [Fragment] that displays more information about the song, along with more media controls.

View file

@ -26,8 +26,6 @@ import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -39,8 +37,10 @@ import org.oxycblt.auxio.playback.queue.QueueAdapter
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.SortMode
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
/**
* The ViewModel that provides a UI frontend for [PlaybackStateManager].

View file

@ -28,15 +28,15 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.HeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.ui.HeaderViewHolder
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logE
/**
* The single adapter for both the Next Queue and the User Queue.

View file

@ -32,11 +32,11 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.isEdgeOn
import org.oxycblt.auxio.isIrregularLandscape
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.isEdgeOn
import org.oxycblt.auxio.util.isIrregularLandscape
/**
* A [Fragment] that contains both the user queue and the next queue, with the ability to

View file

@ -1,41 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* QueueItem.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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.state
/**
* A database entity that stores a simplified representation of a song in a queue.
* @property id The database entity's id
* @property songHash The hash for the song represented
* @property albumHash The hash for the album represented
* @property isUserQueue A bool for if this queue item is a user queue item or not
* @author OxygenCobalt
*/
data class DatabaseQueueItem(
var id: Long = 0L,
val songHash: Int,
val albumHash: Int,
val isUserQueue: Boolean = false
) {
companion object {
const val COLUMN_ID = "id"
const val COLUMN_SONG_HASH = "song"
const val COLUMN_ALBUM_HASH = "album"
const val COLUMN_IS_USER_QUEUE = "is_user_queue"
}
}

View file

@ -1,55 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* PlaybackState.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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.state
/**
* A database entity that stores a compressed variant of the current playback state.
* @property id - The database key for this state
* @property songHash - The hash for the currently playing song
* @property parentHash - The hash for the currently playing parent
* @property index - The current index in the queue.
* @property mode - The integer form of the current [org.oxycblt.auxio.playback.state.PlaybackMode]
* @property isShuffling - A bool for if the queue was shuffled
* @property loopMode - The integer form of the current [org.oxycblt.auxio.playback.state.LoopMode]
* @property inUserQueue - A bool for if the state was currently playing from the user queue.
* @author OxygenCobalt
*/
data class DatabaseState(
val id: Long = 0L,
val songHash: Int,
val position: Long,
val parentHash: Int,
val index: Int,
val mode: Int,
val isShuffling: Boolean,
val loopMode: Int,
val inUserQueue: Boolean
) {
companion object {
const val COLUMN_ID = "state_id"
const val COLUMN_SONG_HASH = "song"
const val COLUMN_POSITION = "position"
const val COLUMN_PARENT_HASH = "parent"
const val COLUMN_INDEX = "_index"
const val COLUMN_MODE = "mode"
const val COLUMN_IS_SHUFFLING = "is_shuffling"
const val COLUMN_LOOP_MODE = "loop_mode"
const val COLUMN_IN_USER_QUEUE = "is_user_queue"
}
}

View file

@ -23,9 +23,9 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.database.sqlite.transaction
import org.oxycblt.auxio.assertBackgroundThread
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.queryAll
import org.oxycblt.auxio.util.assertBackgroundThread
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.queryAll
/**
* A SQLite database for managing the persistent playback state and queue.
@ -273,3 +273,61 @@ class PlaybackStateDatabase(context: Context) :
}
}
}
/**
* A database entity that stores a simplified representation of a song in a queue.
* @property id The database entity's id
* @property songHash The hash for the song represented
* @property albumHash The hash for the album represented
* @property isUserQueue A bool for if this queue item is a user queue item or not
* @author OxygenCobalt
*/
data class DatabaseQueueItem(
var id: Long = 0L,
val songHash: Int,
val albumHash: Int,
val isUserQueue: Boolean = false
) {
companion object {
const val COLUMN_ID = "id"
const val COLUMN_SONG_HASH = "song"
const val COLUMN_ALBUM_HASH = "album"
const val COLUMN_IS_USER_QUEUE = "is_user_queue"
}
}
/**
* A database entity that stores a compressed variant of the current playback state.
* @property id - The database key for this state
* @property songHash - The hash for the currently playing song
* @property parentHash - The hash for the currently playing parent
* @property index - The current index in the queue.
* @property mode - The integer form of the current [org.oxycblt.auxio.playback.state.PlaybackMode]
* @property isShuffling - A bool for if the queue was shuffled
* @property loopMode - The integer form of the current [org.oxycblt.auxio.playback.state.LoopMode]
* @property inUserQueue - A bool for if the state was currently playing from the user queue.
* @author OxygenCobalt
*/
data class DatabaseState(
val id: Long = 0L,
val songHash: Int,
val position: Long,
val parentHash: Int,
val index: Int,
val mode: Int,
val isShuffling: Boolean,
val loopMode: Int,
val inUserQueue: Boolean
) {
companion object {
const val COLUMN_ID = "state_id"
const val COLUMN_SONG_HASH = "song"
const val COLUMN_POSITION = "position"
const val COLUMN_PARENT_HASH = "parent"
const val COLUMN_INDEX = "_index"
const val COLUMN_MODE = "mode"
const val COLUMN_IS_SHUFFLING = "is_shuffling"
const val COLUMN_LOOP_MODE = "loop_mode"
const val COLUMN_IN_USER_QUEUE = "is_user_queue"
}
}

View file

@ -21,8 +21,6 @@ package org.oxycblt.auxio.playback.state
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -30,6 +28,8 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
/**
* Master class (and possible god object) for the playback state.

View file

@ -25,10 +25,10 @@ import androidx.core.animation.addListener
import androidx.media.AudioFocusRequestCompat
import androidx.media.AudioManagerCompat
import com.google.android.exoplayer2.SimpleExoPlayer
import org.oxycblt.auxio.getSystemServiceSafe
import org.oxycblt.auxio.logD
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
/**
* Object that manages the AudioFocus state.

View file

@ -31,10 +31,10 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.loadBitmap
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.newBroadcastIntent
import org.oxycblt.auxio.newMainIntent
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.newBroadcastIntent
import org.oxycblt.auxio.util.newMainIntent
/**
* The unified notification for [PlaybackService]. This is not self-sufficient, updates have

View file

@ -52,14 +52,14 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.getSystemServiceSafe
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Parent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toURI
import org.oxycblt.auxio.playback.state.LoopMode
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.widgets.WidgetController
import org.oxycblt.auxio.widgets.WidgetProvider

View file

@ -27,11 +27,11 @@ import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import com.google.android.exoplayer2.Player
import org.oxycblt.auxio.coil.loadBitmap
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD
/**
* Nightmarish class that coordinates communication between [MediaSessionCompat], [Player],

View file

@ -1,76 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
* BaseViewHolder.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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.recycler
import android.view.View
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.music.BaseModel
/**
* A [RecyclerView.ViewHolder] that streamlines a lot of the common things across all viewholders.
* @param T The datatype, inheriting [BaseModel] for this ViewHolder.
* @param binding Basic [ViewDataBinding] required to set up click listeners & sizing.
* @param doOnClick (Optional) Function that calls on a click.
* @param doOnLongClick (Optional) Functions that calls on a long-click.
* @author OxygenCobalt
*/
abstract class BaseViewHolder<T : BaseModel>(
private val binding: ViewDataBinding,
private val doOnClick: ((data: T) -> Unit)? = null,
private val doOnLongClick: ((view: View, data: T) -> Unit)? = null
) : 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
)
}
/**
* Bind the viewholder with whatever [BaseModel] 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)
}
}
doOnLongClick?.let { onLongClick ->
binding.root.setOnLongClickListener { view ->
onLongClick(view, data)
true
}
}
onBind(data)
binding.executePendingBindings()
}
/**
* 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)
}

View file

@ -28,12 +28,12 @@ import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.AlbumViewHolder
import org.oxycblt.auxio.recycler.ArtistViewHolder
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.GenreViewHolder
import org.oxycblt.auxio.recycler.HeaderViewHolder
import org.oxycblt.auxio.recycler.SongViewHolder
import org.oxycblt.auxio.ui.AlbumViewHolder
import org.oxycblt.auxio.ui.ArtistViewHolder
import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.ui.GenreViewHolder
import org.oxycblt.auxio.ui.HeaderViewHolder
import org.oxycblt.auxio.ui.SongViewHolder
/**
* A Multi-ViewHolder adapter that displays the results of a search query.

View file

@ -29,13 +29,10 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSearchBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.getSystemServiceSafe
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
@ -43,9 +40,11 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.spans
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD
/**
* A [Fragment] that allows for the searching of the entire music library.
@ -107,32 +106,26 @@ class SearchFragment : Fragment() {
}
}
binding.searchEditText.addTextChangedListener { text ->
binding.searchEditText.apply {
addTextChangedListener { text ->
// Run the search with the updated text as the query
searchModel.doSearch(text?.toString() ?: "", requireContext())
}
// Auto-open the keyboard when this view is shown
requestFocus()
postDelayed(200) {
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}
binding.searchRecycler.apply {
adapter = searchAdapter
// It's expensive to calculate the spans for each position in the list, so cache it.
val spans = spans
if (spans != -1) {
layoutManager = GridLayoutManager(requireContext(), spans).apply {
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int =
if (searchAdapter.currentList[position] is Header) spans else 1
applySpans { pos ->
searchAdapter.currentList[pos] is Header
}
}
}
}
// Auto-open the keyboard
binding.searchEditText.requestFocus()
binding.searchEditText.postDelayed(200) {
imm.showSoftInput(binding.searchEditText, InputMethodManager.SHOW_IMPLICIT)
}
// --- VIEWMODEL SETUP ---
@ -179,7 +172,7 @@ class SearchFragment : Fragment() {
}
private fun InputMethodManager.hide() {
hideSoftInputFromWindow(requireView().windowToken, InputMethodManager.HIDE_IMPLICIT_ONLY)
hideSoftInputFromWindow(requireView().windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}
/**
@ -193,8 +186,6 @@ class SearchFragment : Fragment() {
return
}
imm.hide()
if (!searchModel.isNavigating) {
searchModel.setNavigating(true)
@ -214,6 +205,8 @@ class SearchFragment : Fragment() {
}
}
)
imm.hide()
}
}
}

View file

@ -29,8 +29,8 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.DisplayMode
/**
* The [ViewModel] for the search functionality

View file

@ -32,9 +32,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentAboutBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.showToast
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
/**
* A [BottomSheetDialogFragment] that shows Auxio's about screen.

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.settings.ui
package org.oxycblt.auxio.settings
import androidx.appcompat.app.AlertDialog
import org.oxycblt.auxio.ui.LifecycleDialog

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.settings.ui
package org.oxycblt.auxio.settings
import android.content.Context
import android.content.res.TypedArray

View file

@ -37,11 +37,6 @@ fun Int.toThemeIcon(): Int {
}
}
/**
* A verbose shortcut for getString(key, null). Used during string pref migrations
*/
fun SharedPreferences.getStringOrNull(key: String): String? = getString(key, null)
/**
* Converts an int preference under [key] to [T] through a [convert] function.
* This is only intended for use for the enums with fromInt functions.

View file

@ -107,6 +107,11 @@ fun handleSongPlayModeCompat(prefs: SharedPreferences): PlaybackMode {
?: PlaybackMode.ALL_SONGS
}
/**
* A verbose shortcut for getString(key, null). Used during string pref migrations
*/
private fun SharedPreferences.getStringOrNull(key: String): String? = getString(key, null)
/**
* Cache of the old keys used in Auxio.
*/

View file

@ -31,12 +31,10 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.accent.AccentDialog
import org.oxycblt.auxio.excluded.ExcludedDialog
import org.oxycblt.auxio.isNight
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.ui.IntListPrefDialog
import org.oxycblt.auxio.settings.ui.IntListPreference
import org.oxycblt.auxio.showToast
import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
/**
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].

View file

@ -25,8 +25,8 @@ import androidx.preference.PreferenceManager
import org.oxycblt.auxio.accent.ACCENTS
import org.oxycblt.auxio.accent.Accent
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.DisplayMode
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.SortMode
/**
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.

View file

@ -33,7 +33,7 @@ import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.showToast
import org.oxycblt.auxio.util.showToast
/**
* Extension method for creating and showing a new [ActionMenu].

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.recycler
package org.oxycblt.auxio.ui
import androidx.recyclerview.widget.DiffUtil
import org.oxycblt.auxio.music.BaseModel

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.recycler
package org.oxycblt.auxio.ui
/**
* An enum for determining what items to show in a given list.

View file

@ -27,7 +27,7 @@ import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.oxycblt.auxio.R
import org.oxycblt.auxio.resolveAttr
import org.oxycblt.auxio.util.resolveAttr
/**
* A wrapper around [DialogFragment] that allows the usage of the standard Auxio lifecycle

View file

@ -26,8 +26,8 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import org.oxycblt.auxio.assertMainThread
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.util.assertMainThread
import org.oxycblt.auxio.util.inflater
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.recycler
package org.oxycblt.auxio.ui
import android.widget.ImageButton
import androidx.annotation.DrawableRes

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Auxio Project
* ModelHolders.kt is part of Auxio.
* ViewHolders.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
@ -16,21 +16,76 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.recycler
package org.oxycblt.auxio.ui
import android.content.Context
import android.view.View
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.inflater
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.inflater
/**
* A [RecyclerView.ViewHolder] that streamlines a lot of the common things across all viewholders.
* @param T The datatype, inheriting [BaseModel] for this ViewHolder.
* @param binding Basic [ViewDataBinding] required to set up click listeners & sizing.
* @param doOnClick (Optional) Function that calls on a click.
* @param doOnLongClick (Optional) Functions that calls on a long-click.
* @author OxygenCobalt
*/
abstract class BaseViewHolder<T : BaseModel>(
private val binding: ViewDataBinding,
private val doOnClick: ((data: T) -> Unit)? = null,
private val doOnLongClick: ((view: View, data: T) -> Unit)? = null
) : 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
)
}
/**
* Bind the viewholder with whatever [BaseModel] 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)
}
}
doOnLongClick?.let { onLongClick ->
binding.root.setOnLongClickListener { view ->
onLongClick(view, data)
true
}
}
onBind(data)
binding.executePendingBindings()
}
/**
* 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].

View file

@ -0,0 +1,127 @@
/*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.util
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.view.LayoutInflater
import android.widget.Toast
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import org.oxycblt.auxio.MainActivity
import kotlin.reflect.KClass
const val INTENT_REQUEST_CODE = 0xA0A0
/**
* 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.
*/
val Context.isNight: Boolean get() =
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
/**
* Convenience method for getting a system service without nullability issues.
* @param T The system service in question.
* @param serviceClass The service's kotlin class [Java class will be used in function call]
* @return The system service
* @throws IllegalArgumentException If the system service cannot be retrieved.
*/
fun <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): T {
return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
"System service ${serviceClass.simpleName} could not be instantiated"
}
}
/**
* 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
)
}
/**
* 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
)
}
/**
* Create a toast using the provided string resource.
*/
fun Context.showToast(@StringRes str: Int) {
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
}
/**
* Convenience method for getting a plural.
* @param pluralsRes Resource for the plural
* @param value Int value for the plural.
* @return The formatted string requested
*/
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
return resources.getQuantityString(pluralsRes, value, value)
}
/**
* Determine if the device is currently in landscape.
*/
fun Context.isLandscape(): Boolean {
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
/**
* Determine if we are in tablet mode or not
*/
fun Context.isTablet(): Boolean {
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE ||
layout == Configuration.SCREENLAYOUT_SIZE_LARGE
}
/**
* Determine if the tablet is XLARGE, ignoring normal tablets.
*/
fun Context.isXLTablet(): Boolean {
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE
}

View file

@ -0,0 +1,49 @@
/*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.util
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.os.Looper
import org.oxycblt.auxio.R
/**
* Shortcut for querying all items in a database and running [block] with the cursor returned.
* Will not run if the cursor is null.
*/
fun <R> 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.
*/
fun assertBackgroundThread() {
check(Looper.myLooper() != Looper.getMainLooper()) {
"This operation must be ran on a background thread."
}
}
/**
* Assert that we are on a foreground thread.
*/
fun assertMainThread() {
check(Looper.myLooper() == Looper.getMainLooper()) {
"This operation must be ran on the main thread"
}
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Auxio Project
* LogUtils.kt is part of Auxio.
* 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
@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio
package org.oxycblt.auxio.util
import android.util.Log
import org.oxycblt.auxio.BuildConfig
// Shortcut functions for logging.
// Yes, I know timber exists but this does what I need.

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Auxio Project
* AuxioUtils.kt is part of Auxio.
* 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
@ -16,40 +16,54 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio
package org.oxycblt.auxio.util
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.content.res.Resources
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Looper
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.WindowManager
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.reflect.KClass
import org.oxycblt.auxio.R
const val INTENT_REQUEST_CODE = 0xA0A0
/**
* Apply the recommended spans for a [RecyclerView].
*
* @param shouldBeFullWidth Optional callback for determining whether an item should be full-width,
* regardless of spans
*/
fun RecyclerView.applySpans(shouldBeFullWidth: ((Int) -> Boolean)? = null) {
val spans = if (context.isLandscape()) {
if (context.isXLTablet()) 3 else 2
} else {
if (context.isXLTablet()) 2 else 1
}
// --- VIEW CONFIGURATION ---
if (spans > 1) {
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
}
}
}
layoutManager = mgr
}
}
/**
* Disable an image button.
@ -68,90 +82,11 @@ fun TextView.setTextColorResource(@ColorRes color: Int) {
setTextColor(color.resolveColor(context))
}
/**
* Get the span count for most RecyclerViews. These probably work right on most displays. Trust me.
*/
val RecyclerView.spans: Int get() =
if (context.isLandscape()) {
if (context.isXLTablet()) 3 else 2
} else {
if (context.isXLTablet()) 2 else 1
}
/**
* Returns whether a recyclerview can scroll.
*/
fun RecyclerView.canScroll(): Boolean = computeVerticalScrollRange() > height
// --- CONVENIENCE ---
/**
* 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.
*/
val Context.isNight: Boolean get() =
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
/**
* Convenience method for getting a system service without nullability issues.
* @param T The system service in question.
* @param serviceClass The service's kotlin class [Java class will be used in function call]
* @return The system service
* @throws IllegalArgumentException If the system service cannot be retrieved.
*/
fun <T : Any> Context.getSystemServiceSafe(serviceClass: KClass<T>): T {
return requireNotNull(ContextCompat.getSystemService(this, serviceClass.java)) {
"System service ${serviceClass.simpleName} could not be instantiated"
}
}
/**
* 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
)
}
/**
* 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
)
}
/**
* Create a toast using the provided string resource.
*/
fun Context.showToast(@StringRes str: Int) {
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
}
/**
* Convenience method for getting a plural.
* @param pluralsRes Resource for the plural
* @param value Int value for the plural.
* @return The formatted string requested
*/
fun Context.getPlural(@PluralsRes pluralsRes: Int, value: Int): String {
return resources.getQuantityString(pluralsRes, value, value)
}
/**
* Resolve a color.
* @param context [Context] required
@ -204,64 +139,10 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int {
}
/**
* Shortcut for querying all items in a database and running [block] with the cursor returned.
* Will not run if the cursor is null.
*/
fun <R> 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.
*/
fun assertBackgroundThread() {
check(Looper.myLooper() != Looper.getMainLooper()) {
"This operation must be ran on a background thread."
}
}
/**
* Assert that we are on a foreground thread.
*/
fun assertMainThread() {
check(Looper.myLooper() == Looper.getMainLooper()) {
"This operation must be ran on the main thread"
}
}
// --- CONFIGURATION ---
/**
* Check if edge is on. Really a glorified version check.
* @return Whether edge is on.
* Check if edge-to-edge is on. Really a glorified version check.
*/
fun isEdgeOn(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
/**
* Determine if the device is currently in landscape.
*/
fun Context.isLandscape(): Boolean {
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
/**
* Determine if we are in tablet mode or not
*/
fun Context.isTablet(): Boolean {
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE ||
layout == Configuration.SCREENLAYOUT_SIZE_LARGE
}
/**
* Determine if the tablet is XLARGE, ignoring normal tablets.
*/
fun Context.isXLTablet(): Boolean {
val layout = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK
return layout == Configuration.SCREENLAYOUT_SIZE_XLARGE
}
/**
* Check if we are in the "Irregular" landscape mode (e.g landscape, but nav bar is on the sides)
* Used to disable most of edge-to-edge if that's the case, as I cant get it to work on this mode.

View file

@ -22,10 +22,10 @@ import android.content.Context
import android.widget.RemoteViews
import androidx.annotation.LayoutRes
import org.oxycblt.auxio.R
import org.oxycblt.auxio.newBroadcastIntent
import org.oxycblt.auxio.newMainIntent
import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.system.PlaybackService
import org.oxycblt.auxio.util.newBroadcastIntent
import org.oxycblt.auxio.util.newMainIntent
private fun createViews(
context: Context,

View file

@ -30,9 +30,9 @@ import android.util.SizeF
import android.widget.RemoteViews
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.coil.loadBitmap
import org.oxycblt.auxio.isLandscape
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.util.isLandscape
import org.oxycblt.auxio.util.logD
/**
* Auxio's one and only appwidget. This widget follows a more unorthodox approach, effectively

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.HeaderViewHolder">
tools:context=".ui.HeaderViewHolder">
<data>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.AlbumViewHolder">
tools:context=".ui.AlbumViewHolder">
<data>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.ArtistViewHolder">
tools:context=".ui.ArtistViewHolder">
<data>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.SongViewHolder">
tools:context=".ui.SongViewHolder">
<data>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.GenreViewHolder">
tools:context=".ui.GenreViewHolder">
<data>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.HeaderViewHolder">
tools:context=".ui.HeaderViewHolder">
<data>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.SongViewHolder">
tools:context=".ui.SongViewHolder">
<data>

View file

@ -4,7 +4,7 @@
app:layout="@layout/item_header"
app:title="@string/set_ui">
<org.oxycblt.auxio.settings.ui.IntListPreference
<org.oxycblt.auxio.settings.IntListPreference
app:defaultValue="@integer/theme_auto"
app:entries="@array/entires_theme"
app:entryValues="@array/values_theme"
@ -93,7 +93,7 @@
app:layout="@layout/item_header"
app:title="@string/set_behavior">
<org.oxycblt.auxio.settings.ui.IntListPreference
<org.oxycblt.auxio.settings.IntListPreference
app:defaultValue="@integer/play_mode_songs"
app:entries="@array/entries_song_playback_mode"
app:entryValues="@array/values_song_playback_mode"