Refactor music widget
This commit is contained in:
parent
71652ffc71
commit
8be19a1309
@ -74,7 +74,7 @@ fun buildTime(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun versionCodeDate(): Int {
|
fun versionCodeDate(): Int {
|
||||||
val df = SimpleDateFormat("yyyyMMdd00")
|
val df = SimpleDateFormat("yyyyMMdd01")
|
||||||
return df.format(Date()).toInt()
|
return df.format(Date()).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,5 +6,4 @@ import org.koin.dsl.module
|
|||||||
|
|
||||||
val musicModule = module {
|
val musicModule = module {
|
||||||
single { MusicRepository(androidContext()) }
|
single { MusicRepository(androidContext()) }
|
||||||
viewModel { MusicViewModel(get()) }
|
|
||||||
}
|
}
|
||||||
@ -6,33 +6,30 @@ import android.content.Intent
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.net.Uri
|
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.graphics.scale
|
import androidx.core.graphics.scale
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.media2.common.MediaItem
|
import androidx.media2.common.MediaItem
|
||||||
import androidx.media2.common.MediaMetadata
|
import androidx.media2.common.MediaMetadata
|
||||||
import androidx.media2.common.SessionPlayer
|
import androidx.media2.common.SessionPlayer
|
||||||
import androidx.media2.session.MediaController
|
import androidx.media2.session.MediaController
|
||||||
import androidx.media2.session.SessionCommandGroup
|
import androidx.media2.session.SessionCommandGroup
|
||||||
import de.mm20.launcher2.ktx.asBitmap
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class MusicRepository(val context: Context) {
|
class MusicRepository(val context: Context) {
|
||||||
|
|
||||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
|
||||||
val playbackState = MutableLiveData<PlaybackState>()
|
val playbackState = MutableStateFlow(PlaybackState.Stopped)
|
||||||
val title = MutableLiveData<String?>()
|
val title = MutableStateFlow<String?>(null)
|
||||||
val artist = MutableLiveData<String?>()
|
val artist = MutableStateFlow<String?>(null)
|
||||||
val album = MutableLiveData<String?>()
|
val album = MutableStateFlow<String?>(null)
|
||||||
val albumArt = MutableLiveData<Bitmap?>()
|
val albumArt = MutableStateFlow<Bitmap?>(null)
|
||||||
|
|
||||||
private var lastPlayer: String? = null
|
private var lastPlayer: String? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -57,7 +54,7 @@ class MusicRepository(val context: Context) {
|
|||||||
private var mediaController: MediaController? = null
|
private var mediaController: MediaController? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
playbackState.postValue(PlaybackState.Stopped)
|
playbackState.value = PlaybackState.Stopped
|
||||||
}
|
}
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
@ -120,9 +117,9 @@ class MusicRepository(val context: Context) {
|
|||||||
val album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)
|
val album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)
|
||||||
|
|
||||||
lastPlayer = mediaController?.connectedToken?.packageName ?: lastPlayer
|
lastPlayer = mediaController?.connectedToken?.packageName ?: lastPlayer
|
||||||
this@MusicRepository.title.postValue(title)
|
this@MusicRepository.title.value = title
|
||||||
this@MusicRepository.artist.postValue(artist)
|
this@MusicRepository.artist.value = artist
|
||||||
this@MusicRepository.album.postValue(album)
|
this@MusicRepository.album.value = album
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
@ -131,7 +128,7 @@ class MusicRepository(val context: Context) {
|
|||||||
putString(PREFS_KEY_ALBUM_ART, if (albumArt == null) "null" else "notnull")
|
putString(PREFS_KEY_ALBUM_ART, if (albumArt == null) "null" else "notnull")
|
||||||
}
|
}
|
||||||
if (albumArt == null) {
|
if (albumArt == null) {
|
||||||
this@MusicRepository.albumArt.postValue(null)
|
this@MusicRepository.albumArt.value = null
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
val size = context.resources.getDimensionPixelSize(R.dimen.album_art_size)
|
val size = context.resources.getDimensionPixelSize(R.dimen.album_art_size)
|
||||||
@ -140,7 +137,7 @@ class MusicRepository(val context: Context) {
|
|||||||
val outStream = file.outputStream()
|
val outStream = file.outputStream()
|
||||||
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream)
|
scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream)
|
||||||
outStream.close()
|
outStream.close()
|
||||||
this@MusicRepository.albumArt.postValue(scaledBitmap)
|
this@MusicRepository.albumArt.value = scaledBitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +154,7 @@ class MusicRepository(val context: Context) {
|
|||||||
SessionPlayer.PLAYER_STATE_PAUSED -> PlaybackState.Paused
|
SessionPlayer.PLAYER_STATE_PAUSED -> PlaybackState.Paused
|
||||||
else -> PlaybackState.Stopped
|
else -> PlaybackState.Stopped
|
||||||
}
|
}
|
||||||
this@MusicRepository.playbackState.postValue(playbackState)
|
this.playbackState.value = playbackState
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasActiveSession: Boolean = mediaController?.isConnected != null
|
val hasActiveSession: Boolean = mediaController?.isConnected != null
|
||||||
@ -228,7 +225,7 @@ class MusicRepository(val context: Context) {
|
|||||||
if (playbackState.value != PlaybackState.Playing) play() else pause()
|
if (playbackState.value != PlaybackState.Playing) play() else pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLaunchIntent(context: Context): PendingIntent {
|
fun getLaunchIntent(): PendingIntent {
|
||||||
mediaController?.sessionActivity?.let {
|
mediaController?.sessionActivity?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
package de.mm20.launcher2.music
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class MusicViewModel(
|
|
||||||
val musicRepository: MusicRepository
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
val title: LiveData<String?> = musicRepository.title
|
|
||||||
val artist: LiveData<String?> = musicRepository.artist
|
|
||||||
val album: LiveData<String?> = musicRepository.album
|
|
||||||
val albumArt: LiveData<Bitmap?> = musicRepository.albumArt
|
|
||||||
val playbackState: LiveData<PlaybackState> = musicRepository.playbackState
|
|
||||||
|
|
||||||
val hasActiveSession : Boolean = musicRepository.hasActiveSession
|
|
||||||
|
|
||||||
fun previous() {
|
|
||||||
musicRepository.previous()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun next() {
|
|
||||||
musicRepository.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun play() {
|
|
||||||
musicRepository.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pause() {
|
|
||||||
musicRepository.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun togglePause() {
|
|
||||||
musicRepository.togglePause()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLaunchIntent(context: Context): PendingIntent {
|
|
||||||
return musicRepository.getLaunchIntent(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun openPlayerChooser(context: Context) {
|
|
||||||
musicRepository.openPlayerChooser(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.search
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import de.mm20.launcher2.search.data.File
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
|
||||||
|
class SearchViewModel: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
fun search(query: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _files = MutableLiveData<List<File>>()
|
||||||
|
val files: LiveData<List<File>> = _files
|
||||||
|
}
|
||||||
@ -21,26 +21,25 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.mm20.launcher2.music.MusicViewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.music.PlaybackState
|
import de.mm20.launcher2.music.PlaybackState
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.ktx.conditional
|
import de.mm20.launcher2.ui.ktx.conditional
|
||||||
import org.koin.androidx.compose.getViewModel
|
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidgetVM
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class,
|
ExperimentalFoundationApi::class,
|
||||||
ExperimentalAnimationGraphicsApi::class
|
ExperimentalAnimationGraphicsApi::class
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun MusicWidget() {
|
fun MusicWidget() {
|
||||||
|
|
||||||
val viewModel: MusicViewModel = getViewModel()
|
val viewModel: MusicWidgetVM = viewModel()
|
||||||
|
|
||||||
val albumArt by viewModel.albumArt.observeAsState()
|
val albumArt by viewModel.albumArt.observeAsState()
|
||||||
val title by viewModel.title.observeAsState()
|
val title by viewModel.title.observeAsState()
|
||||||
@ -91,7 +90,7 @@ fun MusicWidget() {
|
|||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.previous()
|
viewModel.onPreviousClick()
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.SkipPrevious,
|
imageVector = Icons.Rounded.SkipPrevious,
|
||||||
@ -99,14 +98,14 @@ fun MusicWidget() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val playPauseIcon = AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_play_pause)
|
val playPauseIcon = AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_play_pause)
|
||||||
IconButton(onClick = { viewModel.togglePause() }) {
|
IconButton(onClick = { viewModel.onPlayClick() }) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = rememberAnimatedVectorPainter(playPauseIcon, atEnd = playbackState == PlaybackState.Playing),
|
painter = rememberAnimatedVectorPainter(playPauseIcon, atEnd = playbackState == PlaybackState.Playing),
|
||||||
contentDescription = ""
|
contentDescription = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
viewModel.next()
|
viewModel.onNextClick()
|
||||||
}) {
|
}) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.SkipNext,
|
imageVector = Icons.Rounded.SkipNext,
|
||||||
@ -120,12 +119,10 @@ fun MusicWidget() {
|
|||||||
.size(144.dp)
|
.size(144.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel
|
viewModel.onAlbumArtClick()
|
||||||
.getLaunchIntent(context)
|
|
||||||
.send()
|
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
viewModel.openPlayerChooser(context)
|
viewModel.onAlbumArtLongClick(context)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.conditional(
|
.conditional(
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher.widgets.music
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.music.MusicRepository
|
||||||
|
import de.mm20.launcher2.music.PlaybackState
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class MusicWidgetVM: ViewModel(), KoinComponent {
|
||||||
|
private val musicRepository: MusicRepository by inject()
|
||||||
|
|
||||||
|
val title: LiveData<String?> = musicRepository.title.asLiveData()
|
||||||
|
val artist: LiveData<String?> = musicRepository.artist.asLiveData()
|
||||||
|
val album: LiveData<String?> = musicRepository.album.asLiveData()
|
||||||
|
val albumArt: LiveData<Bitmap?> = musicRepository.albumArt.asLiveData()
|
||||||
|
val playbackState: LiveData<PlaybackState> = musicRepository.playbackState.asLiveData()
|
||||||
|
|
||||||
|
fun onPreviousClick() {
|
||||||
|
musicRepository.previous()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNextClick() {
|
||||||
|
musicRepository.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPlayClick() {
|
||||||
|
musicRepository.togglePause()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAlbumArtClick() {
|
||||||
|
try {
|
||||||
|
musicRepository.getLaunchIntent().send()
|
||||||
|
} catch (e: PendingIntent.CanceledException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAlbumArtLongClick(context: Context) {
|
||||||
|
musicRepository.openPlayerChooser(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,30 +1,16 @@
|
|||||||
package de.mm20.launcher2.ui.legacy.widget
|
package de.mm20.launcher2.ui.legacy.widget
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.graphics.alpha
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import de.mm20.launcher2.ktx.dp
|
|
||||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
|
||||||
import de.mm20.launcher2.music.MusicViewModel
|
|
||||||
import de.mm20.launcher2.music.PlaybackState
|
|
||||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.databinding.CompactMusicBinding
|
|
||||||
import de.mm20.launcher2.ui.widget.MusicWidget
|
import de.mm20.launcher2.ui.widget.MusicWidget
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
|
||||||
|
|
||||||
class MusicWidget : LauncherWidget {
|
class MusicWidget : LauncherWidget {
|
||||||
|
|
||||||
@ -33,8 +19,6 @@ class MusicWidget : LauncherWidget {
|
|||||||
override val name: String
|
override val name: String
|
||||||
get() = context.getString(R.string.widget_name_music)
|
get() = context.getString(R.string.widget_name_music)
|
||||||
|
|
||||||
private val viewModel: MusicViewModel by (context as AppCompatActivity).viewModel()
|
|
||||||
|
|
||||||
constructor(context: Context) : super(context)
|
constructor(context: Context) : super(context)
|
||||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user