diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index b1e8da16d..3caa59cb5 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -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? { diff --git a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt index 822ae7abb..cd18ee15e 100644 --- a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt @@ -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 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() + } + } } diff --git a/app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt b/app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt index 110f0fa72..5e9375a32 100644 --- a/app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/loading/LoadingViewModel.kt @@ -26,10 +26,19 @@ class LoadingViewModel(private val app: Application) : ViewModel() { private val mDoRetry = MutableLiveData() val doRetry: LiveData get() = mDoRetry - init { - startMusicRepo() + private val mDoGrant = MutableLiveData() + val doGrant: LiveData 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() diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index d253ac347..ba5e5bf3b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -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 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 { diff --git a/app/src/main/java/org/oxycblt/auxio/music/processing/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/processing/MusicLoader.kt index c71fbcdfb..a3c81e0f1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/processing/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/processing/MusicLoader.kt @@ -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. diff --git a/app/src/main/res/layout/fragment_loading.xml b/app/src/main/res/layout/fragment_loading.xml index fa9eb7204..5f36c7244 100644 --- a/app/src/main/res/layout/fragment_loading.xml +++ b/app/src/main/res/layout/fragment_loading.xml @@ -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" /> +