Add file intent
Add the ability to view a music file in auxio when selecting the file from another app. The ability to play it will be added later.
This commit is contained in:
parent
e631ddd730
commit
2dfd916736
9 changed files with 73 additions and 5 deletions
|
@ -26,7 +26,19 @@
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.APP_MUSIC" />
|
||||||
|
<action android:name="android.intent.action.MUSIC_PLAYER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data android:scheme="content" />
|
||||||
|
<data android:mimeType="audio/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -18,7 +18,6 @@ import org.oxycblt.auxio.ui.isEdgeOn
|
||||||
* The single [AppCompatActivity] for Auxio.
|
* The single [AppCompatActivity] for Auxio.
|
||||||
*/
|
*/
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -47,6 +46,15 @@ class MainActivity : AppCompatActivity() {
|
||||||
startService(Intent(this, PlaybackService::class.java))
|
startService(Intent(this, PlaybackService::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent?) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
|
// Since the activity is set to singleTask [Given that theres only MainActivity]
|
||||||
|
// We have to manually push the intent whenever we get one so that MainFragment
|
||||||
|
// can catch any file intents
|
||||||
|
setIntent(intent)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private fun doEdgeToEdgeSetup(binding: ActivityMainBinding) {
|
private fun doEdgeToEdgeSetup(binding: ActivityMainBinding) {
|
||||||
window?.apply {
|
window?.apply {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.oxycblt.auxio
|
package org.oxycblt.auxio
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -117,6 +118,17 @@ class MainFragment : Fragment() {
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
val intent = requireActivity().intent
|
||||||
|
|
||||||
|
if (intent != null && intent.action == Intent.ACTION_VIEW) {
|
||||||
|
logD("Attempting to navigate from file intent")
|
||||||
|
detailModel.navigateWithIntent(intent, requireActivity().application)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package org.oxycblt.auxio.detail
|
package org.oxycblt.auxio.detail
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Intent
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.BaseModel
|
import org.oxycblt.auxio.music.BaseModel
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
import org.oxycblt.auxio.music.MusicStore
|
||||||
import org.oxycblt.auxio.recycler.SortMode
|
import org.oxycblt.auxio.recycler.SortMode
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,6 +45,8 @@ class DetailViewModel : ViewModel() {
|
||||||
private val mNavToItem = MutableLiveData<BaseModel?>()
|
private val mNavToItem = MutableLiveData<BaseModel?>()
|
||||||
val navToItem: LiveData<BaseModel?> get() = mNavToItem
|
val navToItem: LiveData<BaseModel?> get() = mNavToItem
|
||||||
|
|
||||||
|
private val musicStore = MusicStore.getInstance()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the current navigation status
|
* Update the current navigation status
|
||||||
* @param value Whether the current [DetailFragment] is navigating or not.
|
* @param value Whether the current [DetailFragment] is navigating or not.
|
||||||
|
@ -107,4 +114,13 @@ class DetailViewModel : ViewModel() {
|
||||||
fun doneWithNavToItem() {
|
fun doneWithNavToItem() {
|
||||||
mNavToItem.value = null
|
mNavToItem.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Navigate to an item using a file [Intent] */
|
||||||
|
fun navigateWithIntent(intent: Intent, app: Application) {
|
||||||
|
val uri = intent.data ?: return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
mNavToItem.value = musicStore.getSongForUri(uri, app.contentResolver)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ class LoadingFragment : Fragment() {
|
||||||
|
|
||||||
if (noPermissions()) {
|
if (noPermissions()) {
|
||||||
// MusicStore.Response.NO_PERMS isnt actually returned by MusicStore, its just
|
// MusicStore.Response.NO_PERMS isnt actually returned by MusicStore, its just
|
||||||
// a way to keep the current permission state on_hand
|
// a way to keep the current permission state across device changes
|
||||||
loadingModel.notifyNoPermissions()
|
loadingModel.notifyNoPermissions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ sealed class Parent : BaseModel()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data object for a song. Inherits [BaseModel].
|
* The data object for a song. Inherits [BaseModel].
|
||||||
|
* @property fileName The raw filename for this track
|
||||||
* @property albumId The Song's Album ID. Never use this outside of when attaching a song to its album.
|
* @property albumId The Song's Album ID. Never use this outside of when attaching a song to its album.
|
||||||
* @property track The Song's Track number
|
* @property track The Song's Track number
|
||||||
* @property duration The duration of the song, in millis.
|
* @property duration The duration of the song, in millis.
|
||||||
|
@ -35,6 +36,7 @@ sealed class Parent : BaseModel()
|
||||||
data class Song(
|
data class Song(
|
||||||
override val id: Long = -1,
|
override val id: Long = -1,
|
||||||
override val name: String,
|
override val name: String,
|
||||||
|
val fileName: String,
|
||||||
val albumId: Long = -1,
|
val albumId: Long = -1,
|
||||||
val track: Int = -1,
|
val track: Int = -1,
|
||||||
val duration: Long = 0,
|
val duration: Long = 0,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.oxycblt.auxio.music
|
package org.oxycblt.auxio.music
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
|
@ -72,6 +75,20 @@ class MusicStore private constructor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the song for a specific URI.
|
||||||
|
*/
|
||||||
|
suspend fun getSongForUri(uri: Uri, resolver: ContentResolver): Song? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
resolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||||
|
cursor.moveToFirst()
|
||||||
|
val fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
||||||
|
|
||||||
|
return@withContext songs.find { it.fileName == fileName }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum class Response {
|
enum class Response {
|
||||||
NO_MUSIC, NO_PERMS, FAILED, SUCCESS
|
NO_MUSIC, NO_PERMS, FAILED, SUCCESS
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,12 +158,13 @@ class MusicLoader(private val app: Application) {
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getLong(idIndex)
|
val id = cursor.getLong(idIndex)
|
||||||
val title = cursor.getString(titleIndex) ?: cursor.getString(fileIndex)
|
val title = cursor.getString(titleIndex)
|
||||||
|
val fileName = cursor.getString(fileIndex)
|
||||||
val albumId = cursor.getLong(albumIndex)
|
val albumId = cursor.getLong(albumIndex)
|
||||||
val track = cursor.getInt(trackIndex)
|
val track = cursor.getInt(trackIndex)
|
||||||
val duration = cursor.getLong(durationIndex)
|
val duration = cursor.getLong(durationIndex)
|
||||||
|
|
||||||
songs.add(Song(id, title, albumId, track, duration))
|
songs.add(Song(id, title ?: fileName, fileName, albumId, track, duration))
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
<string name="setting_behavior_rewind_prev">Zurückspulen, bevor zurück springen</string>
|
<string name="setting_behavior_rewind_prev">Zurückspulen, bevor zurück springen</string>
|
||||||
<string name="setting_behavior_rewind_prev_desc">Zurückspulen, bevor zum vorheriger Lied springen</string>
|
<string name="setting_behavior_rewind_prev_desc">Zurückspulen, bevor zum vorheriger Lied springen</string>
|
||||||
<string name="setting_behavior_save" >Wiedergabezustand abspeichern</string>
|
<string name="setting_behavior_save" >Wiedergabezustand abspeichern</string>
|
||||||
<string name="setting_behavior_save_desc">der aktuell Wiedergabezustand jetzt abspeichern</string>
|
<string name="setting_behavior_save_desc">Der aktuell Wiedergabezustand jetzt abspeichern</string>
|
||||||
|
|
||||||
<!-- Error Namespace | Error Labels -->
|
<!-- Error Namespace | Error Labels -->
|
||||||
<string name="error_no_music">Keine Musik gefunden</string>
|
<string name="error_no_music">Keine Musik gefunden</string>
|
||||||
|
|
Loading…
Reference in a new issue