loading: improve new ui
Improve the loading UI somewhat, reverting the progress bar and applying some padding to the icon so that it looks a bit better on Android 12.
This commit is contained in:
parent
fe29e01311
commit
a5f65d39a5
8 changed files with 65 additions and 65 deletions
|
@ -54,10 +54,7 @@ class LibraryFragment : Fragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentLibraryBinding.inflate(inflater)
|
val binding = FragmentLibraryBinding.inflate(inflater)
|
||||||
|
val libraryAdapter = LibraryAdapter(::navToDetail, ::newMenu)
|
||||||
val libraryAdapter = LibraryAdapter(::navToDetail) { view, data ->
|
|
||||||
newMenu(view, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentLoadingBinding
|
import org.oxycblt.auxio.databinding.FragmentLoadingBinding
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.music.MusicStore
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
|
import org.oxycblt.auxio.ui.isLandscape
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that handles what to display during the loading process.
|
* Fragment that handles what to display during the loading process.
|
||||||
|
@ -58,6 +59,10 @@ class LoadingFragment : Fragment() {
|
||||||
binding.lifecycleOwner = viewLifecycleOwner
|
binding.lifecycleOwner = viewLifecycleOwner
|
||||||
binding.loadingModel = loadingModel
|
binding.loadingModel = loadingModel
|
||||||
|
|
||||||
|
// The loading panel shouldn't fit the system window on landscape as that will cause it
|
||||||
|
// to be mis-aligned with the Auxio icon.
|
||||||
|
binding.loadingPanel.fitsSystemWindows = !isLandscape(resources)
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant ->
|
loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant ->
|
||||||
|
@ -141,7 +146,6 @@ class LoadingFragment : Fragment() {
|
||||||
*/
|
*/
|
||||||
private fun showLoading(binding: FragmentLoadingBinding) {
|
private fun showLoading(binding: FragmentLoadingBinding) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
loadingErrorIcon.visibility = View.INVISIBLE
|
|
||||||
loadingErrorText.visibility = View.INVISIBLE
|
loadingErrorText.visibility = View.INVISIBLE
|
||||||
loadingActionButton.visibility = View.INVISIBLE
|
loadingActionButton.visibility = View.INVISIBLE
|
||||||
loadingCircle.visibility = View.VISIBLE
|
loadingCircle.visibility = View.VISIBLE
|
||||||
|
@ -155,7 +159,6 @@ class LoadingFragment : Fragment() {
|
||||||
*/
|
*/
|
||||||
private fun showError(binding: FragmentLoadingBinding, error: MusicStore.Response) {
|
private fun showError(binding: FragmentLoadingBinding, error: MusicStore.Response) {
|
||||||
binding.loadingCircle.visibility = View.GONE
|
binding.loadingCircle.visibility = View.GONE
|
||||||
binding.loadingErrorIcon.visibility = View.VISIBLE
|
|
||||||
binding.loadingErrorText.visibility = View.VISIBLE
|
binding.loadingErrorText.visibility = View.VISIBLE
|
||||||
binding.loadingActionButton.visibility = View.VISIBLE
|
binding.loadingActionButton.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
|
|
@ -32,16 +32,34 @@ sealed class BaseModel {
|
||||||
abstract val name: String
|
abstract val name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a versatile static hash for a music item that will not change when
|
||||||
|
* MediaStore changes.
|
||||||
|
*
|
||||||
|
* The reason why this is used is down a couple of reasons:
|
||||||
|
* - MediaStore will refresh the unique ID of a piece of media whenever the library
|
||||||
|
* changes, which creates bad UX
|
||||||
|
* - Using song names makes collisions too common to be reliable
|
||||||
|
* - Hashing into an integer makes databases both smaller and more efficent
|
||||||
|
*
|
||||||
|
* This does lock me into a "Load everything at once, lol" architecture for Auxio, but I
|
||||||
|
* think its worth it.
|
||||||
|
*
|
||||||
|
* @property hash A unique-ish hash for this media item
|
||||||
|
*
|
||||||
|
* TODO: Make this hash stronger
|
||||||
|
*/
|
||||||
|
sealed interface Hashable {
|
||||||
|
val hash: Int
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [BaseModel] variant that denotes that this object is a parent of other data objects, such
|
* [BaseModel] variant that denotes that this object is a parent of other data objects, such
|
||||||
* as an [Album] or [Artist]
|
* as an [Album] or [Artist]
|
||||||
* @property hash A versatile, unique(ish) hash used for databases
|
|
||||||
* @property displayName Name that handles the usage of [Genre.resolvedName]
|
* @property displayName Name that handles the usage of [Genre.resolvedName]
|
||||||
* and the normal [BaseModel.name]
|
* and the normal [BaseModel.name]
|
||||||
*/
|
*/
|
||||||
sealed class Parent : BaseModel() {
|
sealed class Parent : BaseModel(), Hashable {
|
||||||
abstract val hash: Int
|
|
||||||
|
|
||||||
val displayName: String get() = if (this is Genre) {
|
val displayName: String get() = if (this is Genre) {
|
||||||
resolvedName
|
resolvedName
|
||||||
} else {
|
} else {
|
||||||
|
@ -61,7 +79,6 @@ sealed class Parent : BaseModel() {
|
||||||
* These are not ensured to be linked due to possible quirks in the genre loading system.
|
* These are not ensured to be linked due to possible quirks in the genre loading system.
|
||||||
* @property seconds The Song's duration in seconds
|
* @property seconds The Song's duration in seconds
|
||||||
* @property formattedDuration The Song's duration as a duration string.
|
* @property formattedDuration The Song's duration as a duration string.
|
||||||
* @property hash A versatile, unique(ish) hash used for databases
|
|
||||||
*/
|
*/
|
||||||
data class Song(
|
data class Song(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
|
@ -70,7 +87,7 @@ data class Song(
|
||||||
val albumId: Long,
|
val albumId: Long,
|
||||||
val track: Int,
|
val track: Int,
|
||||||
val duration: Long
|
val duration: Long
|
||||||
) : BaseModel() {
|
) : BaseModel(), Hashable {
|
||||||
private var mAlbum: Album? = null
|
private var mAlbum: Album? = null
|
||||||
private var mGenre: Genre? = null
|
private var mGenre: Genre? = null
|
||||||
|
|
||||||
|
@ -80,7 +97,12 @@ data class Song(
|
||||||
val seconds = duration / 1000
|
val seconds = duration / 1000
|
||||||
val formattedDuration = seconds.toDuration()
|
val formattedDuration = seconds.toDuration()
|
||||||
|
|
||||||
val hash = songHash()
|
override val hash: Int get() {
|
||||||
|
var result = name.hashCode()
|
||||||
|
result = 31 * result + track
|
||||||
|
result = 31 * result + duration.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
fun linkAlbum(album: Album) {
|
fun linkAlbum(album: Album) {
|
||||||
if (mAlbum == null) {
|
if (mAlbum == null) {
|
||||||
|
@ -93,13 +115,6 @@ data class Song(
|
||||||
mGenre = genre
|
mGenre = genre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun songHash(): Int {
|
|
||||||
var result = name.hashCode()
|
|
||||||
result = 31 * result + track
|
|
||||||
result = 31 * result + duration.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,7 +142,12 @@ data class Album(
|
||||||
val totalDuration: String get() =
|
val totalDuration: String get() =
|
||||||
songs.sumOf { it.seconds }.toDuration()
|
songs.sumOf { it.seconds }.toDuration()
|
||||||
|
|
||||||
override val hash = albumHash()
|
override val hash: Int get() {
|
||||||
|
var result = name.hashCode()
|
||||||
|
result = 31 * result + artistName.hashCode()
|
||||||
|
result = 31 * result + year
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
fun linkArtist(artist: Artist) {
|
fun linkArtist(artist: Artist) {
|
||||||
mArtist = artist
|
mArtist = artist
|
||||||
|
@ -139,13 +159,6 @@ data class Album(
|
||||||
mSongs.add(song)
|
mSongs.add(song)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun albumHash(): Int {
|
|
||||||
var result = name.hashCode()
|
|
||||||
result = 31 * result + artistName.hashCode()
|
|
||||||
result = 31 * result + year
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -60,9 +60,7 @@ class SearchFragment : Fragment() {
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentSearchBinding.inflate(inflater)
|
val binding = FragmentSearchBinding.inflate(inflater)
|
||||||
|
|
||||||
val searchAdapter = SearchAdapter(::onItemSelection) { view, data ->
|
val searchAdapter = SearchAdapter(::onItemSelection, ::newMenu)
|
||||||
newMenu(view, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
val toolbarParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
val toolbarParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||||
val defaultParams = toolbarParams.scrollFlags
|
val defaultParams = toolbarParams.scrollFlags
|
||||||
|
|
|
@ -49,9 +49,7 @@ class SongsFragment : Fragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val binding = FragmentSongsBinding.inflate(inflater)
|
val binding = FragmentSongsBinding.inflate(inflater)
|
||||||
val songAdapter = SongsAdapter(musicStore.songs, playbackModel::playSong) { view, data ->
|
val songAdapter = SongsAdapter(musicStore.songs, playbackModel::playSong, ::newMenu)
|
||||||
newMenu(view, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- UI SETUP ---
|
// --- UI SETUP ---
|
||||||
|
|
||||||
|
|
|
@ -16,73 +16,61 @@
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<!-- TODO: Get this splash icon working with Android 12 splashes -->
|
<!--
|
||||||
|
TODO: Get this to line up with Android 12's splash screen when I can figure
|
||||||
|
out what they do
|
||||||
|
-->
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/loading_splash"
|
android:id="@+id/loading_splash"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/desc_auxio_icon"
|
android:contentDescription="@string/desc_auxio_icon"
|
||||||
android:scaleType="fitCenter"
|
android:padding="@dimen/spacing_huge"
|
||||||
android:src="@drawable/ic_launcher_foreground" />
|
android:src="@drawable/ic_launcher_foreground" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/constraintLayout"
|
android:id="@+id/loading_panel"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="@dimen/spacing_large"
|
android:layout_margin="@dimen/spacing_large"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
<ProgressBar
|
||||||
android:id="@+id/loading_circle"
|
android:id="@+id/loading_circle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:indeterminate="true"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/loading_action_button"
|
app:layout_constraintBottom_toBottomOf="@+id/loading_action_button"
|
||||||
app:layout_constraintTop_toTopOf="@+id/loading_error_icon" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/loading_error_text"
|
android:id="@+id/loading_error_text"
|
||||||
android:fontFamily="@font/inter_semibold"
|
|
||||||
android:textSize="@dimen/text_size_mid_large"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="@dimen/spacing_small"
|
android:layout_marginBottom="@dimen/spacing_small"
|
||||||
|
android:fontFamily="@font/inter_semibold"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/text_size_mid_large"
|
||||||
|
android:visibility="invisible"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/loading_action_button"
|
app:layout_constraintBottom_toTopOf="@+id/loading_action_button"
|
||||||
tools:text="No Music Found" />
|
tools:text="No Music Found" />
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/loading_error_icon"
|
|
||||||
android:layout_width="@dimen/size_error_icon"
|
|
||||||
android:layout_height="@dimen/size_error_icon"
|
|
||||||
android:layout_marginBottom="@dimen/spacing_small"
|
|
||||||
android:contentDescription="@string/desc_error"
|
|
||||||
android:src="@drawable/ic_error"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/loading_error_text"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/loading_action_button"
|
android:id="@+id/loading_action_button"
|
||||||
style="@style/Widget.Button.Vibrant.Primary"
|
style="@style/Widget.Button.Vibrant.Primary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
android:paddingStart="@dimen/spacing_insane"
|
||||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
android:paddingEnd="@dimen/spacing_insane"
|
||||||
android:text="@string/lbl_retry"
|
android:text="@string/lbl_retry"
|
||||||
android:visibility="gone"
|
android:visibility="invisible"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<dimen name="spacing_mid_large">24dp</dimen>
|
<dimen name="spacing_mid_large">24dp</dimen>
|
||||||
<dimen name="spacing_large">32dp</dimen>
|
<dimen name="spacing_large">32dp</dimen>
|
||||||
<dimen name="spacing_mid_huge">48dp</dimen>
|
<dimen name="spacing_mid_huge">48dp</dimen>
|
||||||
|
<dimen name="spacing_huge">64dp</dimen>
|
||||||
<dimen name="spacing_insane">128dp</dimen>
|
<dimen name="spacing_insane">128dp</dimen>
|
||||||
|
|
||||||
<!-- Height Namespace | Height for UI elements -->
|
<!-- Height Namespace | Height for UI elements -->
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- COMPONENT-SPECIFIC STYLES. NOT RE-USABLE. -->
|
<!-- COMPONENT-SPECIFIC STYLES. NOT RE-USABLE. -->
|
||||||
|
|
||||||
<!-- Style for the play/pause circle button -->
|
|
||||||
<style name="Widget.Component.Playback.PlayPause" parent="">
|
<style name="Widget.Component.Playback.PlayPause" parent="">
|
||||||
<item name="android:layout_height">@dimen/size_play_pause</item>
|
<item name="android:layout_height">@dimen/size_play_pause</item>
|
||||||
<item name="android:layout_width">@dimen/size_play_pause</item>
|
<item name="android:layout_width">@dimen/size_play_pause</item>
|
||||||
|
@ -18,7 +17,6 @@
|
||||||
<item name="android:padding">@dimen/spacing_medium</item>
|
<item name="android:padding">@dimen/spacing_medium</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Style for text used in widgets -->
|
|
||||||
<style name="Widget.Component.AppWidget.TextView" parent="Widget.AppCompat.TextView">
|
<style name="Widget.Component.AppWidget.TextView" parent="Widget.AppCompat.TextView">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
@ -77,4 +75,8 @@
|
||||||
<style name="Widget.Component.AppWidget.Panel" parent="Widget.Component.AppWidget.Panel.Base">
|
<style name="Widget.Component.AppWidget.Panel" parent="Widget.Component.AppWidget.Panel.Base">
|
||||||
<item name="android:elevation">@dimen/elevation_normal</item>
|
<item name="android:elevation">@dimen/elevation_normal</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Component.Loading.ImageView.Icon" parent="">
|
||||||
|
<item name="android:src">@drawable/ic_launcher_foreground</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue