Rewrite loading UI
Completely rewrite the loading UI to be far more understandable.
This commit is contained in:
parent
92e9e3282c
commit
e3e0015237
12 changed files with 150 additions and 230 deletions
|
@ -11,6 +11,7 @@
|
|||
<img alt="Minimum SDK" src="https://img.shields.io/badge/API-21%2B-32B5ED">
|
||||
</p>
|
||||
<h4 align="center"><a href="/info/FAQ.md">FAQ</a> / <a href="/info/FORMATS.md">Formats</a> / <a href="/info/LICENSES.md">Licenses</a> / <a href="/.github/CONTRIBUTING.md">Contributing</a></h4>
|
||||
<p align="center"><a href="https://apt.izzysoft.de/fdroid/index/apk/org.oxycblt.auxio"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="170"></a></p>
|
||||
|
||||
## About
|
||||
|
||||
|
|
|
@ -13,17 +13,9 @@ 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.music.processing.MusicLoader
|
||||
|
||||
/**
|
||||
* An intermediary [Fragment] that asks for the READ_EXTERNAL_STORAGE permission and runs
|
||||
* the music loading process in the background.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
||||
// LoadingViewModel is scoped to this fragment only
|
||||
class LoadingFragment : Fragment() {
|
||||
private val loadingModel: LoadingViewModel by viewModels {
|
||||
LoadingViewModel.Factory(requireActivity().application)
|
||||
}
|
||||
|
@ -35,115 +27,110 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
|||
): View {
|
||||
val binding = FragmentLoadingBinding.inflate(inflater)
|
||||
|
||||
// Set up the permission launcher, as its disallowed outside of onCreate.
|
||||
val permLauncher =
|
||||
registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { granted: Boolean ->
|
||||
// If its actually granted, restart the loading process again.
|
||||
if (granted) {
|
||||
returnToLoading(binding)
|
||||
|
||||
loadingModel.reload()
|
||||
} else {
|
||||
showError(binding)
|
||||
|
||||
binding.loadingGrantButton.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.text = getString(R.string.error_no_perms)
|
||||
}
|
||||
}
|
||||
// Build the permission launcher here as you can only do it in onCreateView/onCreate
|
||||
val permLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(), ::onPermResult
|
||||
)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
binding.lifecycleOwner = this
|
||||
binding.loadingModel = loadingModel
|
||||
|
||||
// --- VIEWMODEL SETUP ---
|
||||
|
||||
loadingModel.response.observe(viewLifecycleOwner) {
|
||||
if (it == MusicLoader.Response.SUCCESS) {
|
||||
findNavController().navigate(
|
||||
LoadingFragmentDirections.actionToMain()
|
||||
)
|
||||
} else {
|
||||
// If the response wasn't a success, then show the specific error message
|
||||
// depending on which error response was given, along with a retry button
|
||||
binding.loadingErrorText.text =
|
||||
if (it == MusicLoader.Response.NO_MUSIC)
|
||||
getString(R.string.error_no_music)
|
||||
else
|
||||
getString(R.string.error_music_load_failed)
|
||||
|
||||
showError(binding)
|
||||
binding.loadingRetryButton.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
loadingModel.doReload.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
returnToLoading(binding)
|
||||
loadingModel.doneWithReload()
|
||||
}
|
||||
}
|
||||
|
||||
loadingModel.doGrant.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
permLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
returnToLoading(binding)
|
||||
loadingModel.doneWithGrant()
|
||||
}
|
||||
}
|
||||
|
||||
// Force an error screen if the permissions are denied or the prompt needs to be shown.
|
||||
if (checkPerms()) {
|
||||
showError(binding)
|
||||
loadingModel.response.observe(viewLifecycleOwner) { response ->
|
||||
when (response) {
|
||||
// Success should lead to Auxio navigating away from the fragment
|
||||
MusicStore.Response.SUCCESS -> findNavController().navigate(
|
||||
LoadingFragmentDirections.actionToMain()
|
||||
)
|
||||
|
||||
binding.loadingGrantButton.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.text = getString(R.string.error_no_perms)
|
||||
} else {
|
||||
loadingModel.go()
|
||||
// Null means that the loading process is going on
|
||||
null -> showLoading(binding)
|
||||
|
||||
// Anything else is an error
|
||||
else -> {
|
||||
showError(binding, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logD("Fragment created.")
|
||||
if (noPermissions()) {
|
||||
// MusicStore.Response.NO_PERMS isnt actually returned by MusicStore, its just
|
||||
// a way to keep the current permission state on_hand
|
||||
loadingModel.notifyNoPermissions()
|
||||
}
|
||||
|
||||
if (loadingModel.response.value == null) {
|
||||
loadingModel.load()
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// --- PERMISSIONS ---
|
||||
|
||||
// If the music was already loaded, then don't do it again.
|
||||
if (MusicStore.getInstance().loaded) {
|
||||
findNavController().navigate(
|
||||
LoadingFragmentDirections.actionToMain()
|
||||
)
|
||||
private fun noPermissions(): Boolean {
|
||||
val needRationale = shouldShowRequestPermissionRationale(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
val notGranted = ContextCompat.checkSelfPermission(
|
||||
requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_DENIED
|
||||
|
||||
return needRationale || notGranted
|
||||
}
|
||||
|
||||
private fun onPermResult(granted: Boolean) {
|
||||
if (granted) {
|
||||
// If granted, its now safe to load [Which will clear the NO_PERMS response we applied
|
||||
// earlier]
|
||||
loadingModel.load()
|
||||
}
|
||||
}
|
||||
|
||||
// Check for two things:
|
||||
// - If Auxio needs to show the rationale for getting the READ_EXTERNAL_STORAGE permission.
|
||||
// - If Auxio straight up doesn't have the READ_EXTERNAL_STORAGE permission.
|
||||
private fun checkPerms(): Boolean {
|
||||
return shouldShowRequestPermissionRationale(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
) || ContextCompat.checkSelfPermission(
|
||||
requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_DENIED
|
||||
// --- UI DISPLAY ---
|
||||
|
||||
private fun showLoading(binding: FragmentLoadingBinding) {
|
||||
binding.apply {
|
||||
loadingCircle.visibility = View.VISIBLE
|
||||
loadingErrorIcon.visibility = View.GONE
|
||||
loadingErrorText.visibility = View.GONE
|
||||
loadingRetryButton.visibility = View.GONE
|
||||
loadingGrantButton.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the loading indicator and show the error groups
|
||||
private fun showError(binding: FragmentLoadingBinding) {
|
||||
binding.loadingBar.visibility = View.GONE
|
||||
private fun showError(binding: FragmentLoadingBinding, error: MusicStore.Response) {
|
||||
binding.loadingCircle.visibility = View.GONE
|
||||
binding.loadingErrorIcon.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
// Wipe views and switch back to the plain ProgressBar
|
||||
private fun returnToLoading(binding: FragmentLoadingBinding) {
|
||||
binding.loadingBar.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.visibility = View.GONE
|
||||
binding.loadingErrorIcon.visibility = View.GONE
|
||||
binding.loadingRetryButton.visibility = View.GONE
|
||||
binding.loadingGrantButton.visibility = View.GONE
|
||||
when (error) {
|
||||
MusicStore.Response.NO_MUSIC -> {
|
||||
binding.loadingRetryButton.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.text = getString(R.string.error_no_music)
|
||||
}
|
||||
|
||||
MusicStore.Response.NO_PERMS -> {
|
||||
binding.loadingGrantButton.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.text = getString(R.string.error_no_perms)
|
||||
}
|
||||
|
||||
MusicStore.Response.FAILED -> {
|
||||
binding.loadingRetryButton.visibility = View.VISIBLE
|
||||
binding.loadingErrorText.text = getString(R.string.error_load_failed)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,87 +6,47 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.processing.MusicLoader
|
||||
|
||||
/**
|
||||
* A [ViewModel] responsible for getting the music loading process going and managing the response
|
||||
* returned.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class LoadingViewModel(private val app: Application) : ViewModel() {
|
||||
private val mResponse = MutableLiveData<MusicLoader.Response>()
|
||||
val response: LiveData<MusicLoader.Response> get() = mResponse
|
||||
private val mResponse = MutableLiveData<MusicStore.Response?>(null)
|
||||
val response: LiveData<MusicStore.Response?> = mResponse
|
||||
|
||||
private val mRedo = MutableLiveData<Boolean>()
|
||||
val doReload: LiveData<Boolean> get() = mRedo
|
||||
private val mDoGrant = MutableLiveData(false)
|
||||
val doGrant: LiveData<Boolean> = mDoGrant
|
||||
|
||||
private val mDoGrant = MutableLiveData<Boolean>()
|
||||
val doGrant: LiveData<Boolean> get() = mDoGrant
|
||||
private var isBusy = false
|
||||
|
||||
private var started = false
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
|
||||
/**
|
||||
* Start the music loading sequence.
|
||||
* This should only be ran once, use reload() for all other loads.
|
||||
*/
|
||||
fun go() {
|
||||
if (!started) {
|
||||
started = true
|
||||
doLoad()
|
||||
}
|
||||
}
|
||||
fun load() {
|
||||
// Dont start a new load if the last one hasnt finished
|
||||
if (isBusy) return
|
||||
|
||||
isBusy = true
|
||||
mResponse.value = null
|
||||
|
||||
private fun doLoad() {
|
||||
viewModelScope.launch {
|
||||
val musicStore = MusicStore.getInstance()
|
||||
|
||||
val response = musicStore.load(app)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
mResponse.value = response
|
||||
}
|
||||
mResponse.value = musicStore.load(app)
|
||||
isBusy = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the music
|
||||
*/
|
||||
fun reload() {
|
||||
mRedo.value = true
|
||||
|
||||
doLoad()
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark that the UI is done with the reload call
|
||||
*/
|
||||
fun doneWithReload() {
|
||||
mRedo.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark to start the grant process
|
||||
*/
|
||||
fun grant() {
|
||||
mDoGrant.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark that the UI is done with the grant process.
|
||||
*/
|
||||
fun doneWithGrant() {
|
||||
mDoGrant.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for [LoadingViewModel] instances.
|
||||
*/
|
||||
fun notifyNoPermissions() {
|
||||
mResponse.value = MusicStore.Response.NO_PERMS
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class Factory(private val application: Application) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(LoadingViewModel::class.java)) {
|
||||
return LoadingViewModel(application) as T
|
||||
|
|
|
@ -41,7 +41,7 @@ class MusicStore private constructor() {
|
|||
* ***THIS SHOULD ONLY BE RAN FROM AN IO THREAD.***
|
||||
* @param app [Application] required to load the music.
|
||||
*/
|
||||
suspend fun load(app: Application): MusicLoader.Response {
|
||||
suspend fun load(app: Application): Response {
|
||||
return withContext(Dispatchers.IO) {
|
||||
this@MusicStore.logD("Starting initial music load...")
|
||||
|
||||
|
@ -50,7 +50,7 @@ class MusicStore private constructor() {
|
|||
val loader = MusicLoader(app)
|
||||
val response = loader.loadMusic()
|
||||
|
||||
if (response == MusicLoader.Response.SUCCESS) {
|
||||
if (response == Response.SUCCESS) {
|
||||
// If the loading succeeds, then sort the songs and update the value
|
||||
val sorter = MusicSorter(loader.songs, loader.albums)
|
||||
|
||||
|
@ -72,6 +72,10 @@ class MusicStore private constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
enum class Response {
|
||||
NO_MUSIC, NO_PERMS, FAILED, SUCCESS
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: MusicStore? = null
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.oxycblt.auxio.logD
|
|||
import org.oxycblt.auxio.logE
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.toAlbumArtURI
|
||||
|
||||
|
@ -26,23 +27,25 @@ class MusicLoader(private val app: Application) {
|
|||
|
||||
private val resolver = app.contentResolver
|
||||
|
||||
fun loadMusic(): Response {
|
||||
fun loadMusic(): MusicStore.Response {
|
||||
try {
|
||||
loadGenres()
|
||||
loadAlbums()
|
||||
loadSongs()
|
||||
} catch (error: Exception) {
|
||||
logE("Something went horribly wrong.")
|
||||
error.printStackTrace()
|
||||
val trace = error.stackTraceToString()
|
||||
|
||||
return Response.FAILED
|
||||
logE("Something went horribly wrong.")
|
||||
logE(trace)
|
||||
|
||||
return MusicStore.Response.FAILED
|
||||
}
|
||||
|
||||
if (songs.isEmpty()) {
|
||||
return Response.NO_MUSIC
|
||||
return MusicStore.Response.NO_MUSIC
|
||||
}
|
||||
|
||||
return Response.SUCCESS
|
||||
return MusicStore.Response.SUCCESS
|
||||
}
|
||||
|
||||
private fun loadGenres() {
|
||||
|
@ -89,7 +92,6 @@ class MusicLoader(private val app: Application) {
|
|||
Albums._ID, // 0
|
||||
Albums.ALBUM, // 1
|
||||
Albums.ARTIST, // 2
|
||||
|
||||
Albums.FIRST_YEAR, // 3
|
||||
),
|
||||
null, null,
|
||||
|
@ -207,8 +209,4 @@ class MusicLoader(private val app: Application) {
|
|||
|
||||
logD("Song search finished with ${songs.size} found")
|
||||
}
|
||||
|
||||
enum class Response {
|
||||
SUCCESS, FAILED, NO_MUSIC
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import android.text.Spanned
|
|||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.text.toSpanned
|
||||
import androidx.core.text.HtmlCompat
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import java.util.Locale
|
||||
|
@ -59,10 +59,10 @@ data class Accent(@ColorRes val color: Int, @StyleRes val theme: Int, @StringRes
|
|||
val name = context.getString(name)
|
||||
val hex = context.getString(color).toUpperCase(Locale.getDefault())
|
||||
|
||||
return context.getString(
|
||||
R.string.format_accent_summary,
|
||||
name, hex
|
||||
).toSpanned().render()
|
||||
return HtmlCompat.fromHtml(
|
||||
context.getString(R.string.format_accent_summary, name, hex),
|
||||
HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -70,8 +70,7 @@ data class Accent(@ColorRes val color: Int, @StyleRes val theme: Int, @StringRes
|
|||
private var current: Accent? = null
|
||||
|
||||
/**
|
||||
* Get the current accent, will default to whatever is stored in [SettingsManager]
|
||||
* if there isnt one.
|
||||
* Get the current accent.
|
||||
* @return The current accent
|
||||
*/
|
||||
fun get(): Accent {
|
||||
|
|
|
@ -8,7 +8,6 @@ import android.content.res.Resources
|
|||
import android.graphics.Point
|
||||
import android.graphics.drawable.AnimatedVectorDrawable
|
||||
import android.os.Build
|
||||
import android.text.Spanned
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -22,7 +21,6 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.annotation.PluralsRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
|
@ -109,16 +107,6 @@ fun Fragment.requireCompatActivity(): AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Render" a [Spanned] using [HtmlCompat]. (As in making text bolded and whatnot).
|
||||
* @return A [Spanned] that actually works.
|
||||
*/
|
||||
fun Spanned.render(): Spanned {
|
||||
return HtmlCompat.fromHtml(
|
||||
this.toString(), HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a color.
|
||||
* @param context [Context] required
|
||||
|
|
|
@ -39,7 +39,7 @@ class SlideLinearLayout @JvmOverloads constructor(
|
|||
|
||||
init {
|
||||
if (disappearingChildrenField != null) {
|
||||
// Create a junk view and add it, which makes all the magic happen [I think].
|
||||
// Create a invisible junk view and add it, which makes all the magic happen [I think].
|
||||
dumpView = View(context)
|
||||
addView(dumpView, 0, 0)
|
||||
}
|
||||
|
@ -58,13 +58,18 @@ class SlideLinearLayout @JvmOverloads constructor(
|
|||
val children = getDisappearingChildren()
|
||||
|
||||
if (doDrawingTrick && children != null) {
|
||||
if (child == dumpView) { // Use the dump view as a marker for when to do the trick
|
||||
var more = false
|
||||
// Use the dump view as a marker for when to do the trick
|
||||
if (child == dumpView) {
|
||||
// I dont even know what this does.
|
||||
var consumed = false
|
||||
|
||||
children.forEach {
|
||||
more = more or super.drawChild(canvas, it, drawingTime) // What????
|
||||
consumed = consumed or super.drawChild(canvas, it, drawingTime)
|
||||
}
|
||||
return more
|
||||
} else if (children.contains(child)) { // Ignore the disappearing children
|
||||
|
||||
return consumed
|
||||
} else if (children.contains(child)) {
|
||||
// Ignore the disappearing children
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<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=".loading.LoadingFragment">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="loadingModel"
|
||||
type="org.oxycblt.auxio.loading.LoadingViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true">
|
||||
android:orientation="vertical"
|
||||
android:animateLayoutChanges="true"
|
||||
android:gravity="center">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_bar"
|
||||
android:id="@+id/loading_circle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminateTint="?attr/colorPrimary"
|
||||
android:indeterminateTintMode="src_in"
|
||||
android:paddingBottom="@dimen/padding_tiny"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loading_error_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:paddingBottom="@dimen/padding_tiny" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/loading_error_icon"
|
||||
|
@ -38,28 +34,19 @@
|
|||
android:indeterminateTint="?attr/colorPrimary"
|
||||
android:indeterminateTintMode="src_in"
|
||||
android:src="@drawable/ic_error"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loading_error_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loading_bar"
|
||||
app:srcCompat="@drawable/ic_error" />
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loading_error_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loading_retry_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loading_error_icon"
|
||||
tools:text="Some kind of error." />
|
||||
tools:text="Some kind of error" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/loading_retry_button"
|
||||
|
@ -67,17 +54,10 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter_semibold"
|
||||
android:onClick="@{() -> loadingModel.reload()}"
|
||||
android:text="@string/label_retry"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loading_grant_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loading_error_text"
|
||||
app:layout_constraintVertical_bias="0.673"
|
||||
android:onClick="@{() -> loadingModel.load()}"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
|
@ -86,15 +66,12 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter_semibold"
|
||||
android:onClick="@{() -> loadingModel.grant()}"
|
||||
android:text="@string/label_grant"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loading_retry_button"
|
||||
android:onClick="@{() -> loadingModel.grant()}"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
|
@ -89,7 +89,7 @@
|
|||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="error_no_music">Keine Musik gefunden</string>
|
||||
<string name="error_music_load_failed">Laden die Musik fehlgeschlagen</string>
|
||||
<string name="error_load_failed">Laden die Musik fehlgeschlagen</string>
|
||||
<string name="error_no_perms">Auxio braucht Berechtigung, zu lesen deine Musikbibliothek</string>
|
||||
<string name="error_no_browser">Link könnte nicht geöffnet werden</string>
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<!-- Label Namespace | Static Labels -->
|
||||
<string name="label_retry">Retry</string>
|
||||
<string name="label_grant">Grant</string>
|
||||
<string name="label_view_trace">View Error</string>
|
||||
|
||||
<string name="label_library">Library</string>
|
||||
<string name="label_genres">Genres</string>
|
||||
|
@ -90,7 +91,7 @@
|
|||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="error_no_music">No music found</string>
|
||||
<string name="error_music_load_failed">Music loading failed</string>
|
||||
<string name="error_load_failed">Music loading failed</string>
|
||||
<string name="error_no_perms">Auxio needs permission to read your music library</string>
|
||||
<string name="error_no_browser">Could not open link</string>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ Currently, its available on the [IzzyOnDroid F-Droid repository](https://apt.izz
|
|||
|
||||
## Can I translate Auxio to my native language?
|
||||
|
||||
See the [Translations](https://github.com/OxygenCobalt/Auxio/issues/3) issue for guidance on how to create translations and submit them to the project. Any contributions are apprieciated.
|
||||
See the [Translations](https://github.com/OxygenCobalt/Auxio/issues/3) issue for guidance on how to create translations and submit them to the project. Any contributions are appreciated.
|
||||
|
||||
## How can I contribute/report issues?
|
||||
|
||||
|
|
Loading…
Reference in a new issue