Add permission dialog for file reading

Add a permission dialog for the apps READ_EXTERNAL_STORAGE permission.
This commit is contained in:
OxygenCobalt 2020-09-02 17:58:13 -06:00
parent 5e6917f11c
commit af26aed735
7 changed files with 123 additions and 15 deletions

View file

@ -9,8 +9,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import org.oxycblt.auxio.theme.accent
const val PERM_READ_EXTERNAL_STORAGE = 2488
class MainActivity : AppCompatActivity() {
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {

View file

@ -1,10 +1,13 @@
package org.oxycblt.auxio.loading
import android.Manifest
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -25,6 +28,7 @@ class LoadingFragment : Fragment() {
}
private lateinit var binding: FragmentLoadingBinding
private lateinit var permLauncher: ActivityResultLauncher<String>
override fun onCreateView(
inflater: LayoutInflater,
@ -52,6 +56,37 @@ class LoadingFragment : Fragment() {
}
)
loadingModel.doGrant.observe(
viewLifecycleOwner,
{ grant ->
onGrant(grant)
}
)
// Set up the permission launcher, as its disallowed outside of onCreate.
permLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission())
{ granted: Boolean ->
// If its actually granted, restart the loading process again.
if (granted) {
binding.loadingBar.visibility = View.VISIBLE
binding.errorText.visibility = View.GONE
binding.statusIcon.visibility = View.GONE
binding.retryButton.visibility = View.GONE
binding.grantButton.visibility = View.GONE
loadingModel.retry()
}
}
// This never seems to return true but Im apparently supposed to use it so
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
onMusicLoadResponse(MusicLoaderResponse.NO_PERMS)
} else {
loadingModel.go()
}
Log.d(this::class.simpleName, "Fragment created.")
return binding.root
@ -65,20 +100,30 @@ class LoadingFragment : Fragment() {
this.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
// depending on which error response was given, along with a retry or grant button
binding.loadingBar.visibility = View.GONE
binding.errorText.visibility = View.VISIBLE
binding.statusIcon.visibility = View.VISIBLE
binding.retryButton.visibility = View.VISIBLE
binding.errorText.text =
if (response == MusicLoaderResponse.NO_MUSIC)
getString(R.string.error_no_music)
else
getString(R.string.error_music_load_failed)
when (response) {
MusicLoaderResponse.NO_PERMS -> {
binding.grantButton.visibility = View.VISIBLE
binding.errorText.text = getString(R.string.error_no_perms)
}
MusicLoaderResponse.NO_MUSIC -> {
binding.retryButton.visibility = View.VISIBLE
binding.errorText.text = getString(R.string.error_no_music)
}
else -> {
binding.retryButton.visibility = View.VISIBLE
binding.errorText.text = getString(R.string.error_music_load_failed)
}
}
}
loadingModel.doneWithResponse()
@ -91,8 +136,17 @@ class LoadingFragment : Fragment() {
binding.errorText.visibility = View.GONE
binding.statusIcon.visibility = View.GONE
binding.retryButton.visibility = View.GONE
binding.grantButton.visibility = View.GONE
loadingModel.doneWithRetry()
}
}
private fun onGrant(grant: Boolean) {
if (grant) {
permLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
loadingModel.doneWithGrant()
}
}
}

View file

@ -26,10 +26,19 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
private val mDoRetry = MutableLiveData<Boolean>()
val doRetry: LiveData<Boolean> get() = mDoRetry
init {
private val mDoGrant = MutableLiveData<Boolean>()
val doGrant: LiveData<Boolean> get() = mDoGrant
private var started = false
// Start the music loading. It has already been called, one needs to call retry() instead.
fun go() {
if (!started) {
startMusicRepo()
}
}
// Start the music loading sequence.
private fun startMusicRepo() {
val repo = MusicRepository.getInstance()
@ -40,10 +49,14 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
// Then actually notify listeners of the response in the Main thread
withContext(Dispatchers.Main) {
mMusicRepoResponse.value = response
started = true
}
}
}
// Functions for communicating between LoadingFragment & LoadingViewModel
fun doneWithResponse() {
mMusicRepoResponse.value = null
}
@ -58,6 +71,14 @@ class LoadingViewModel(private val app: Application) : ViewModel() {
mDoRetry.value = false
}
fun grant() {
mDoGrant.value = true
}
fun doneWithGrant() {
mDoGrant.value = false
}
override fun onCleared() {
super.onCleared()

View file

@ -1,7 +1,10 @@
package org.oxycblt.auxio.music
import android.Manifest
import android.app.Application
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.content.ContextCompat
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.music.models.Artist
@ -20,6 +23,12 @@ class MusicRepository {
lateinit var songs: List<Song>
fun init(app: Application): MusicLoaderResponse {
if (!checkPerms(app)) {
Log.i(this::class.simpleName, "No permissions, aborting...")
return MusicLoaderResponse.NO_PERMS
}
Log.i(this::class.simpleName, "Starting initial music load...")
val start = System.currentTimeMillis()
@ -52,7 +61,13 @@ class MusicRepository {
)
}
return MusicLoaderResponse.NO_MUSIC
return loader.response
}
private fun checkPerms(app: Application): Boolean {
return ContextCompat.checkSelfPermission(
app.applicationContext, Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
companion object {

View file

@ -15,7 +15,7 @@ import org.oxycblt.auxio.music.toAlbumArtURI
import org.oxycblt.auxio.music.toNamedGenre
enum class MusicLoaderResponse {
DONE, FAILURE, NO_MUSIC
DONE, FAILURE, NO_MUSIC, NO_PERMS
}
// Class that loads music from the FileSystem.

View file

@ -39,6 +39,7 @@
android:indeterminateTintMode="src_in"
android:src="@drawable/ic_error"
android:contentDescription="@string/description_error_icon"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/error_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -76,9 +77,27 @@
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/error_text"
app:layout_constraintBottom_toTopOf="@+id/grant_button"
app:layout_constraintVertical_bias="0.673"
tools:textColor="@color/blue"
tools:visibility="visible" />
<Button
android:id="@+id/grant_button"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
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/retry_button"
tools:textColor="@color/blue"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -7,9 +7,10 @@
<string name="error_no_music">No music found.</string>
<string name="error_music_load_failed">Music loading failed.</string>
<string name="error_no_perms">Auxio needs to be allowed to read your music library.</string>
<string name="error_no_perms">Auxio needs access to your music library.</string>
<string name="label_retry">Retry</string>
<string name="label_grant">Grant</string>
<string name="label_single_song">1 Song</string>
<string name="label_unknown_genre">Unknown Genre</string>
<string name="label_unknown_artist">Unknown Artist</string>