all: add viewmodel contexts where useful
Make some ViewModel instances AndroidViewModels in order to make some code less insane. I don't like doing this, as I want to keep ViewModel instances clean of android things, but this just makes a lot of functionality easier to implement.
This commit is contained in:
parent
543a3ebffb
commit
107f7bee27
6 changed files with 71 additions and 52 deletions
|
@ -17,10 +17,10 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.detail
|
package org.oxycblt.auxio.detail
|
||||||
|
|
||||||
import android.content.Context
|
import android.app.Application
|
||||||
import android.media.MediaExtractor
|
import android.media.MediaExtractor
|
||||||
import android.media.MediaFormat
|
import android.media.MediaFormat
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
@ -40,6 +40,7 @@ import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.ui.Header
|
import org.oxycblt.auxio.ui.Header
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
|
import org.oxycblt.auxio.util.application
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
@ -51,7 +52,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
* - Menu triggers for each fragment
|
* - Menu triggers for each fragment
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class DetailViewModel : ViewModel(), MusicStore.Callback {
|
class DetailViewModel(application: Application) :
|
||||||
|
AndroidViewModel(application), MusicStore.Callback {
|
||||||
data class DetailSong(
|
data class DetailSong(
|
||||||
val song: Song,
|
val song: Song,
|
||||||
val bitrateKbps: Int?,
|
val bitrateKbps: Int?,
|
||||||
|
@ -109,11 +111,11 @@ class DetailViewModel : ViewModel(), MusicStore.Callback {
|
||||||
currentGenre.value?.let(::refreshGenreData)
|
currentGenre.value?.let(::refreshGenreData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSongId(context: Context, id: Long) {
|
fun setSongId(id: Long) {
|
||||||
if (_currentSong.value?.run { song.id } == id) return
|
if (_currentSong.value?.run { song.id } == id) return
|
||||||
val library = unlikelyToBeNull(musicStore.library)
|
val library = unlikelyToBeNull(musicStore.library)
|
||||||
val song = requireNotNull(library.songs.find { it.id == id }) { "Invalid song id provided" }
|
val song = requireNotNull(library.songs.find { it.id == id }) { "Invalid song id provided" }
|
||||||
generateDetailSong(context, song)
|
generateDetailSong(song)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearSong() {
|
fun clearSong() {
|
||||||
|
@ -152,14 +154,15 @@ class DetailViewModel : ViewModel(), MusicStore.Callback {
|
||||||
musicStore.addCallback(this)
|
musicStore.addCallback(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateDetailSong(context: Context, song: Song) {
|
private fun generateDetailSong(song: Song) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_currentSong.value =
|
_currentSong.value =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val extractor = MediaExtractor()
|
val extractor = MediaExtractor()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
extractor.setDataSource(context, song.uri, emptyMap())
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
extractor.setDataSource(application, song.uri, emptyMap())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logW("Unable to extract song attributes.")
|
logW("Unable to extract song attributes.")
|
||||||
logW(e.stackTraceToString())
|
logW(e.stackTraceToString())
|
||||||
|
@ -250,14 +253,13 @@ class DetailViewModel : ViewModel(), MusicStore.Callback {
|
||||||
|
|
||||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||||
if (library != null) {
|
if (library != null) {
|
||||||
// TODO: Add when we have a context
|
val song = currentSong.value
|
||||||
// val song = currentSong.value
|
if (song != null) {
|
||||||
// if (song != null) {
|
val newSong = library.sanitize(song.song)
|
||||||
// val newSong = library.sanitize(song.song)
|
if (newSong != null) {
|
||||||
// if (newSong != null) {
|
generateDetailSong(newSong)
|
||||||
// generateDetailSong(newSong)
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
val album = currentAlbum.value
|
val album = currentAlbum.value
|
||||||
if (album != null) {
|
if (album != null) {
|
||||||
|
|
|
@ -44,7 +44,7 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
|
||||||
|
|
||||||
override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) {
|
override fun onBindingCreated(binding: DialogSongDetailBinding, savedInstanceState: Bundle?) {
|
||||||
super.onBindingCreated(binding, savedInstanceState)
|
super.onBindingCreated(binding, savedInstanceState)
|
||||||
detailModel.setSongId(requireContext(), requireNotNull(arguments).getLong(ARG_ID))
|
detailModel.setSongId(requireNotNull(arguments).getLong(ARG_ID))
|
||||||
launch { detailModel.currentSong.collect(::updateSong) }
|
launch { detailModel.currentSong.collect(::updateSong) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,15 +28,12 @@ import androidx.core.view.postDelayed
|
||||||
import androidx.core.widget.addTextChangedListener
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.FragmentSearchBinding
|
import org.oxycblt.auxio.databinding.FragmentSearchBinding
|
||||||
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.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Indexer
|
|
||||||
import org.oxycblt.auxio.music.IndexerViewModel
|
|
||||||
import org.oxycblt.auxio.music.Music
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.MusicParent
|
import org.oxycblt.auxio.music.MusicParent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
@ -47,6 +44,7 @@ import org.oxycblt.auxio.ui.MenuItemListener
|
||||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
|
import org.oxycblt.auxio.util.androidViewModels
|
||||||
import org.oxycblt.auxio.util.applySpans
|
import org.oxycblt.auxio.util.applySpans
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||||
import org.oxycblt.auxio.util.launch
|
import org.oxycblt.auxio.util.launch
|
||||||
|
@ -61,10 +59,9 @@ class SearchFragment :
|
||||||
MenuItemListener,
|
MenuItemListener,
|
||||||
Toolbar.OnMenuItemClickListener {
|
Toolbar.OnMenuItemClickListener {
|
||||||
// SearchViewModel is only scoped to this Fragment
|
// SearchViewModel is only scoped to this Fragment
|
||||||
private val searchModel: SearchViewModel by viewModels()
|
private val searchModel: SearchViewModel by androidViewModels()
|
||||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||||
private val navModel: NavigationViewModel by activityViewModels()
|
private val navModel: NavigationViewModel by activityViewModels()
|
||||||
private val indexerModel: IndexerViewModel by activityViewModels()
|
|
||||||
|
|
||||||
private val searchAdapter = SearchAdapter(this)
|
private val searchAdapter = SearchAdapter(this)
|
||||||
private var imm: InputMethodManager? = null
|
private var imm: InputMethodManager? = null
|
||||||
|
@ -87,7 +84,7 @@ class SearchFragment :
|
||||||
binding.searchEditText.apply {
|
binding.searchEditText.apply {
|
||||||
addTextChangedListener { text ->
|
addTextChangedListener { text ->
|
||||||
// Run the search with the updated text as the query
|
// Run the search with the updated text as the query
|
||||||
searchModel.search(context, text?.toString())
|
searchModel.search(text?.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!launchedKeyboard) {
|
if (!launchedKeyboard) {
|
||||||
|
@ -109,7 +106,6 @@ class SearchFragment :
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
launch { searchModel.searchResults.collect(::updateResults) }
|
launch { searchModel.searchResults.collect(::updateResults) }
|
||||||
launch { indexerModel.state.collect(::handleIndexerState) }
|
|
||||||
launch { navModel.exploreNavigationItem.collect(::handleNavigation) }
|
launch { navModel.exploreNavigationItem.collect(::handleNavigation) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +120,7 @@ class SearchFragment :
|
||||||
R.id.submenu_filtering -> {}
|
R.id.submenu_filtering -> {}
|
||||||
else -> {
|
else -> {
|
||||||
if (item.itemId != R.id.submenu_filtering) {
|
if (item.itemId != R.id.submenu_filtering) {
|
||||||
searchModel.updateFilterModeWithId(requireContext(), item.itemId)
|
searchModel.updateFilterModeWithId(item.itemId)
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,12 +167,6 @@ class SearchFragment :
|
||||||
requireImm().hide()
|
requireImm().hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIndexerState(state: Indexer.State?) {
|
|
||||||
if (state is Indexer.State.Complete && state.response is Indexer.Response.Ok) {
|
|
||||||
searchModel.refresh(requireContext())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requireImm(): InputMethodManager {
|
private fun requireImm(): InputMethodManager {
|
||||||
requireAttached()
|
requireAttached()
|
||||||
val instance = imm
|
val instance = imm
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.search
|
package org.oxycblt.auxio.search
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import java.text.Normalizer
|
import java.text.Normalizer
|
||||||
|
@ -33,16 +35,15 @@ import org.oxycblt.auxio.ui.DisplayMode
|
||||||
import org.oxycblt.auxio.ui.Header
|
import org.oxycblt.auxio.ui.Header
|
||||||
import org.oxycblt.auxio.ui.Item
|
import org.oxycblt.auxio.ui.Item
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
|
import org.oxycblt.auxio.util.application
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [ViewModel] for search functionality
|
* The [ViewModel] for search functionality.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*
|
|
||||||
* TODO: Add a context to this ViewModel, not because I want to, but because it just makes the code
|
|
||||||
* easier to work with.
|
|
||||||
*/
|
*/
|
||||||
class SearchViewModel : ViewModel() {
|
class SearchViewModel(application: Application) :
|
||||||
|
AndroidViewModel(application), MusicStore.Callback {
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
private val settingsManager = SettingsManager.getInstance()
|
private val settingsManager = SettingsManager.getInstance()
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ class SearchViewModel : ViewModel() {
|
||||||
/**
|
/**
|
||||||
* Use [query] to perform a search of the music library. Will push results to [searchResults].
|
* Use [query] to perform a search of the music library. Will push results to [searchResults].
|
||||||
*/
|
*/
|
||||||
fun search(context: Context, query: String?) {
|
fun search(query: String?) {
|
||||||
lastQuery = query
|
lastQuery = query
|
||||||
|
|
||||||
val library = musicStore.library
|
val library = musicStore.library
|
||||||
|
@ -84,28 +85,28 @@ class SearchViewModel : ViewModel() {
|
||||||
// Note: a filter mode of null means to not filter at all.
|
// Note: a filter mode of null means to not filter at all.
|
||||||
|
|
||||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ARTISTS) {
|
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ARTISTS) {
|
||||||
library.artists.filterByOrNull(context, query)?.let { artists ->
|
library.artists.filterByOrNull(query)?.let { artists ->
|
||||||
results.add(Header(-1, R.string.lbl_artists))
|
results.add(Header(-1, R.string.lbl_artists))
|
||||||
results.addAll(sort.artists(artists))
|
results.addAll(sort.artists(artists))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ALBUMS) {
|
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ALBUMS) {
|
||||||
library.albums.filterByOrNull(context, query)?.let { albums ->
|
library.albums.filterByOrNull(query)?.let { albums ->
|
||||||
results.add(Header(-2, R.string.lbl_albums))
|
results.add(Header(-2, R.string.lbl_albums))
|
||||||
results.addAll(sort.albums(albums))
|
results.addAll(sort.albums(albums))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_GENRES) {
|
if (_filterMode == null || _filterMode == DisplayMode.SHOW_GENRES) {
|
||||||
library.genres.filterByOrNull(context, query)?.let { genres ->
|
library.genres.filterByOrNull(query)?.let { genres ->
|
||||||
results.add(Header(-3, R.string.lbl_genres))
|
results.add(Header(-3, R.string.lbl_genres))
|
||||||
results.addAll(sort.genres(genres))
|
results.addAll(sort.genres(genres))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_SONGS) {
|
if (_filterMode == null || _filterMode == DisplayMode.SHOW_SONGS) {
|
||||||
library.songs.filterByOrNull(context, query)?.let { songs ->
|
library.songs.filterByOrNull(query)?.let { songs ->
|
||||||
results.add(Header(-4, R.string.lbl_songs))
|
results.add(Header(-4, R.string.lbl_songs))
|
||||||
results.addAll(sort.songs(songs))
|
results.addAll(sort.songs(songs))
|
||||||
}
|
}
|
||||||
|
@ -115,15 +116,10 @@ class SearchViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Re-search the library using the last query. Will push results to [searchResults]. */
|
|
||||||
fun refresh(context: Context) {
|
|
||||||
search(context, lastQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the current filter mode with a menu [id]. New value will be pushed to [filterMode].
|
* Update the current filter mode with a menu [id]. New value will be pushed to [filterMode].
|
||||||
*/
|
*/
|
||||||
fun updateFilterModeWithId(context: Context, @IdRes id: Int) {
|
fun updateFilterModeWithId(@IdRes id: Int) {
|
||||||
_filterMode =
|
_filterMode =
|
||||||
when (id) {
|
when (id) {
|
||||||
R.id.option_filter_songs -> DisplayMode.SHOW_SONGS
|
R.id.option_filter_songs -> DisplayMode.SHOW_SONGS
|
||||||
|
@ -137,20 +133,20 @@ class SearchViewModel : ViewModel() {
|
||||||
|
|
||||||
settingsManager.searchFilterMode = _filterMode
|
settingsManager.searchFilterMode = _filterMode
|
||||||
|
|
||||||
refresh(context)
|
search(lastQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut that will run a ignoreCase filter on a list and only return a value if the resulting
|
* Shortcut that will run a ignoreCase filter on a list and only return a value if the resulting
|
||||||
* list is empty.
|
* list is empty.
|
||||||
*/
|
*/
|
||||||
private fun <T : Music> List<T>.filterByOrNull(context: Context, value: String): List<T>? {
|
private fun <T : Music> List<T>.filterByOrNull(value: String): List<T>? {
|
||||||
val filtered = filter {
|
val filtered = filter {
|
||||||
// Compare normalized names, which are names with unicode characters that are
|
// Compare normalized names, which are names with unicode characters that are
|
||||||
// normalized to their non-unicode forms. This is just for quality-of-life,
|
// normalized to their non-unicode forms. This is just for quality-of-life,
|
||||||
// and I hope it doesn't bork search functionality for other languages.
|
// and I hope it doesn't bork search functionality for other languages.
|
||||||
it.resolveNameNormalized(context).contains(value, ignoreCase = true) ||
|
it.resolveNameNormalized(application).contains(value, ignoreCase = true) ||
|
||||||
it.resolveNameNormalized(context).contains(value, ignoreCase = true)
|
it.resolveNameNormalized(application).contains(value, ignoreCase = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered.ifEmpty { null }
|
return filtered.ifEmpty { null }
|
||||||
|
@ -185,4 +181,16 @@ class SearchViewModel : ViewModel() {
|
||||||
|
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||||
|
if (library != null) {
|
||||||
|
// Make sure our query is up to date with the music library.
|
||||||
|
search(lastQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
musicStore.removeCallback(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.util
|
package org.oxycblt.auxio.util
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
@ -31,7 +32,11 @@ import androidx.core.graphics.Insets
|
||||||
import androidx.core.graphics.drawable.DrawableCompat
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
@ -168,6 +173,20 @@ fun Fragment.launch(
|
||||||
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(state, block) }
|
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(state, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Fragment.androidViewModelFactory() =
|
||||||
|
ViewModelProvider.AndroidViewModelFactory(requireContext().applicationContext as Application)
|
||||||
|
|
||||||
|
inline fun <reified T : AndroidViewModel> Fragment.androidViewModels() =
|
||||||
|
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) }
|
||||||
|
|
||||||
|
inline fun <reified T : AndroidViewModel> Fragment.activityAndroidViewModels() =
|
||||||
|
activityViewModels<T> {
|
||||||
|
ViewModelProvider.AndroidViewModelFactory(requireActivity().application)
|
||||||
|
}
|
||||||
|
|
||||||
|
val AndroidViewModel.application: Application
|
||||||
|
get() = getApplication()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines the called flow with the given flow and then collects them both into [block]. This is a
|
* Combines the called flow with the given flow and then collects them both into [block]. This is a
|
||||||
* bit of a dumb hack with [combine], as when we have to combine flows, we often just want to call
|
* bit of a dumb hack with [combine], as when we have to combine flows, we often just want to call
|
||||||
|
|
|
@ -14,6 +14,6 @@
|
||||||
<string name="cdc_wav">Microsoft WAVE</string>
|
<string name="cdc_wav">Microsoft WAVE</string>
|
||||||
|
|
||||||
<!-- Note: These are stopgap measures until we make the path code rely on components! -->
|
<!-- Note: These are stopgap measures until we make the path code rely on components! -->
|
||||||
<string name="fmt_primary_path">Internal:%s</string>
|
<string name="fmt_primary_path">Internal/%s</string>
|
||||||
<string name="fmt_secondary_path">SDCARD:%s</string>
|
<string name="fmt_secondary_path">SDCARD/%s</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue