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?
|
||||
): View {
|
||||
val binding = FragmentLibraryBinding.inflate(inflater)
|
||||
|
||||
val libraryAdapter = LibraryAdapter(::navToDetail) { view, data ->
|
||||
newMenu(view, data)
|
||||
}
|
||||
val libraryAdapter = LibraryAdapter(::navToDetail, ::newMenu)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ 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.ui.isLandscape
|
||||
|
||||
/**
|
||||
* Fragment that handles what to display during the loading process.
|
||||
|
@ -58,6 +59,10 @@ class LoadingFragment : Fragment() {
|
|||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
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 ---
|
||||
|
||||
loadingModel.doGrant.observe(viewLifecycleOwner) { doGrant ->
|
||||
|
@ -141,7 +146,6 @@ class LoadingFragment : Fragment() {
|
|||
*/
|
||||
private fun showLoading(binding: FragmentLoadingBinding) {
|
||||
binding.apply {
|
||||
loadingErrorIcon.visibility = View.INVISIBLE
|
||||
loadingErrorText.visibility = View.INVISIBLE
|
||||
loadingActionButton.visibility = View.INVISIBLE
|
||||
loadingCircle.visibility = View.VISIBLE
|
||||
|
@ -155,7 +159,6 @@ class LoadingFragment : Fragment() {
|
|||
*/
|
||||
private fun showError(binding: FragmentLoadingBinding, error: MusicStore.Response) {
|
||||
binding.loadingCircle.visibility = View.GONE
|
||||
binding.loadingErrorIcon.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.visibility = View.VISIBLE
|
||||
binding.loadingActionButton.visibility = View.VISIBLE
|
||||
|
||||
|
|
|
@ -32,16 +32,34 @@ sealed class BaseModel {
|
|||
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
|
||||
* 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]
|
||||
* and the normal [BaseModel.name]
|
||||
*/
|
||||
sealed class Parent : BaseModel() {
|
||||
abstract val hash: Int
|
||||
|
||||
sealed class Parent : BaseModel(), Hashable {
|
||||
val displayName: String get() = if (this is Genre) {
|
||||
resolvedName
|
||||
} 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.
|
||||
* @property seconds The Song's duration in seconds
|
||||
* @property formattedDuration The Song's duration as a duration string.
|
||||
* @property hash A versatile, unique(ish) hash used for databases
|
||||
*/
|
||||
data class Song(
|
||||
override val id: Long,
|
||||
|
@ -70,7 +87,7 @@ data class Song(
|
|||
val albumId: Long,
|
||||
val track: Int,
|
||||
val duration: Long
|
||||
) : BaseModel() {
|
||||
) : BaseModel(), Hashable {
|
||||
private var mAlbum: Album? = null
|
||||
private var mGenre: Genre? = null
|
||||
|
||||
|
@ -80,7 +97,12 @@ data class Song(
|
|||
val seconds = duration / 1000
|
||||
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) {
|
||||
if (mAlbum == null) {
|
||||
|
@ -93,13 +115,6 @@ data class Song(
|
|||
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() =
|
||||
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) {
|
||||
mArtist = artist
|
||||
|
@ -139,13 +159,6 @@ data class Album(
|
|||
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 {
|
||||
val binding = FragmentSearchBinding.inflate(inflater)
|
||||
|
||||
val searchAdapter = SearchAdapter(::onItemSelection) { view, data ->
|
||||
newMenu(view, data)
|
||||
}
|
||||
val searchAdapter = SearchAdapter(::onItemSelection, ::newMenu)
|
||||
|
||||
val toolbarParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||
val defaultParams = toolbarParams.scrollFlags
|
||||
|
|
|
@ -49,9 +49,7 @@ class SongsFragment : Fragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val binding = FragmentSongsBinding.inflate(inflater)
|
||||
val songAdapter = SongsAdapter(musicStore.songs, playbackModel::playSong) { view, data ->
|
||||
newMenu(view, data)
|
||||
}
|
||||
val songAdapter = SongsAdapter(musicStore.songs, playbackModel::playSong, ::newMenu)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
|
|
@ -16,73 +16,61 @@
|
|||
android:gravity="center"
|
||||
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
|
||||
android:id="@+id/loading_splash"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/desc_auxio_icon"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="@dimen/spacing_huge"
|
||||
android:src="@drawable/ic_launcher_foreground" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/constraintLayout"
|
||||
android:id="@+id/loading_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/spacing_large"
|
||||
android:animateLayoutChanges="true"
|
||||
android:layout_gravity="bottom"
|
||||
android:fitsSystemWindows="true"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_circle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/loading_action_button"
|
||||
app:layout_constraintTop_toTopOf="@+id/loading_error_icon" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
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_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/spacing_small"
|
||||
android:fontFamily="@font/inter_semibold"
|
||||
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"
|
||||
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
|
||||
android:id="@+id/loading_action_button"
|
||||
style="@style/Widget.Button.Vibrant.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
||||
android:paddingStart="@dimen/spacing_insane"
|
||||
android:paddingEnd="@dimen/spacing_insane"
|
||||
android:text="@string/lbl_retry"
|
||||
android:visibility="gone"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:visibility="visible" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<dimen name="spacing_mid_large">24dp</dimen>
|
||||
<dimen name="spacing_large">32dp</dimen>
|
||||
<dimen name="spacing_mid_huge">48dp</dimen>
|
||||
<dimen name="spacing_huge">64dp</dimen>
|
||||
<dimen name="spacing_insane">128dp</dimen>
|
||||
|
||||
<!-- Height Namespace | Height for UI elements -->
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<resources>
|
||||
<!-- COMPONENT-SPECIFIC STYLES. NOT RE-USABLE. -->
|
||||
|
||||
<!-- Style for the play/pause circle button -->
|
||||
<style name="Widget.Component.Playback.PlayPause" parent="">
|
||||
<item name="android:layout_height">@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>
|
||||
</style>
|
||||
|
||||
<!-- Style for text used in widgets -->
|
||||
<style name="Widget.Component.AppWidget.TextView" parent="Widget.AppCompat.TextView">
|
||||
<item name="android:layout_width">match_parent</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">
|
||||
<item name="android:elevation">@dimen/elevation_normal</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Component.Loading.ImageView.Icon" parent="">
|
||||
<item name="android:src">@drawable/ic_launcher_foreground</item>
|
||||
</style>
|
||||
</resources>
|
Loading…
Reference in a new issue