Skip to content

Commit

Permalink
Several desktop improvements, add download location selection
Browse files Browse the repository at this point in the history
  • Loading branch information
toasterofbread committed Nov 25, 2023
1 parent 0295f05 commit 5edf002
Show file tree
Hide file tree
Showing 36 changed files with 590 additions and 165 deletions.
2 changes: 1 addition & 1 deletion ComposeKit
8 changes: 1 addition & 7 deletions desktopApp/src/jvmMain/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.skiko.OS
Expand Down Expand Up @@ -76,13 +75,8 @@ fun main() {
process_builder.inheritIO().start()
}
catch (e: Throwable) {
e.printStackTrace()
RuntimeException("Execution of startup command failed", e).printStackTrace()
}

delay(2000)
context.sendToast("This is a toast")
delay(2000)
context.sendNotification("And this", "Is a goddamn notification")
}
}

Expand Down
7 changes: 7 additions & 0 deletions shared/src/androidMain/kotlin/PlatformTheme.android.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import androidx.compose.runtime.Composable
import com.toasterofbread.composekit.settings.ui.Theme

@Composable
internal actual fun PlatformTheme(theme: Theme, content: @Composable () -> Unit) {
content()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.content.PermissionChecker
import androidx.core.graphics.drawable.IconCompat
import com.google.common.io.Files
import com.toasterofbread.composekit.platform.PlatformFile
import com.toasterofbread.spmp.model.lyrics.LyricsFileConverter
import com.toasterofbread.spmp.model.mediaitem.library.MediaItemLibrary
Expand Down Expand Up @@ -44,6 +45,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.net.ConnectException
Expand All @@ -65,8 +67,12 @@ class PlayerDownloadService: PlatformServiceImpl() {
val quality: SongAudioQuality,
var silent: Boolean,
val instance: Int,
val file_uri: String?
) {
var song_file: PlatformFile? = runBlocking { song.getLocalSongFile(context, allow_partial = true) } // This is fine :)
var song_file: PlatformFile? = runBlocking {
// This is fine :)
song.getLocalSongFile(context, allow_partial = true)
}
var lyrics_file: PlatformFile? = song.getLocalLyricsFile(context, allow_partial = true)

var status: DownloadStatus.Status =
Expand Down Expand Up @@ -132,14 +138,14 @@ class PlayerDownloadService: PlatformServiceImpl() {
}

private var download_inc: Int = 0
private fun getOrCreateDownload(context: AppContext, song: Song): Download {
private fun getOrCreateDownload(context: AppContext, song: Song, silent: Boolean, file_uri: String?): Download {
synchronized(downloads) {
for (download in downloads) {
if (download.song.id == song.id) {
return download
}
}
return Download(context, song, Settings.getEnum(StreamingSettings.Key.DOWNLOAD_AUDIO_QUALITY), true, download_inc++)
return Download(context, song, Settings.getEnum(StreamingSettings.Key.DOWNLOAD_AUDIO_QUALITY), silent, download_inc++, file_uri)
}
}

Expand Down Expand Up @@ -279,12 +285,12 @@ class PlayerDownloadService: PlatformServiceImpl() {
private fun startDownload(message: PlayerDownloadManager.PlayerDownloadMessage) {
require(message.instance != null)

val download = getOrCreateDownload(context, SongRef(message.data["song_id"] as String))

val silent = message.data["silent"] as Boolean
if (!silent) {
download.silent = false
}
val download: Download = getOrCreateDownload(
context,
SongRef(message.data["song_id"] as String),
silent = message.data["silent"] as Boolean,
file_uri = message.data["file_uri"] as String?
)

synchronized(download) {
if (download.finished) {
Expand Down Expand Up @@ -418,29 +424,29 @@ class PlayerDownloadService: PlatformServiceImpl() {
))
}

val format_extension: String =
when (connection.contentType) {
"audio/webm" -> "webm"
"audio/mp4" -> "mp4"
else -> return@withContext Result.failure(NotImplementedError(connection.contentType))
}

var file: PlatformFile? = download.song_file
check(song_download_dir.mkdirs()) { song_download_dir.toString() }

val file_extension: String = when (connection.contentType) {
"audio/webm" -> "webm"
"audio/mp4" -> "mp4"
else -> return@withContext Result.failure(NotImplementedError(connection.contentType))
}

if (file == null) {
file = song_download_dir.resolve(download.generatePath(file_extension, true))
file = song_download_dir.resolve(download.generatePath(format_extension, true))
}

check(file.name.endsWith(FILE_DOWNLOADING_SUFFIX))
val target_filename: String = download.generatePath(file_extension, false)

val data: ByteArray = ByteArray(4096)
val output: OutputStream = file.outputStream(true)
val input: InputStream = connection.inputStream
val input_stream: InputStream = connection.inputStream
val output_stream: OutputStream = file.outputStream(true)

fun close(status: DownloadStatus.Status) {
input.close()
output.close()
input_stream.close()
output_stream.close()
connection.disconnect()
download.status = status
}
Expand All @@ -455,7 +461,7 @@ class PlayerDownloadService: PlatformServiceImpl() {
download.status = DownloadStatus.Status.DOWNLOADING

while (true) {
val size = input.read(data)
val size = input_stream.read(data)
if (size < 0) {
break
}
Expand All @@ -472,7 +478,7 @@ class PlayerDownloadService: PlatformServiceImpl() {
}

download.downloaded += size
output.write(data, 0, size)
output_stream.write(data, 0, size)

onDownloadProgress()
}
Expand All @@ -485,7 +491,7 @@ class PlayerDownloadService: PlatformServiceImpl() {

runBlocking {
launch {
LocalSongMetadataProcessor.addMetadataToLocalSong(download.song, file, target_filename, context)
LocalSongMetadataProcessor.addMetadataToLocalSong(download.song, file, format_extension, context)
}
launch {
if (download.lyrics_file == null) {
Expand All @@ -507,8 +513,21 @@ class PlayerDownloadService: PlatformServiceImpl() {
close(DownloadStatus.Status.CANCELLED)
}

val renamed: PlatformFile = file.renameTo(target_filename)
download.song_file = renamed
if (download.file_uri != null) {
val uri_file: PlatformFile = context.getUserDirectoryFile(download.file_uri)
uri_file.outputStream().use { output ->
Files.copy(File(file.absolute_path), output)
}
file.delete()
download.song_file = uri_file
}
else {
val renamed: PlatformFile = file.renameTo(
download.generatePath(format_extension, false)
)
download.song_file = renamed
}

download.status = DownloadStatus.Status.FINISHED

return@withContext Result.success(download.song_file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ internal suspend fun processMediaDataSpec(data_spec: DataSpec, context: AppConte
when (initial_status?.status) {
PlayerDownloadManager.DownloadStatus.Status.IDLE, PlayerDownloadManager.DownloadStatus.Status.CANCELLED, PlayerDownloadManager.DownloadStatus.Status.PAUSED, null -> {
download_manager.startDownload(song, true) { status ->
local_file = status.file
local_file = status?.file
done = true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.toasterofbread.spmp.model.mediaitem.MediaItemData
import com.toasterofbread.spmp.model.mediaitem.artist.Artist.Companion.getForItemId
import com.toasterofbread.spmp.model.mediaitem.artist.ArtistData
import com.toasterofbread.spmp.model.mediaitem.artist.ArtistRef
import com.toasterofbread.spmp.model.mediaitem.playlist.PlaylistData
import com.toasterofbread.spmp.model.mediaitem.playlist.RemotePlaylist
import com.toasterofbread.spmp.model.mediaitem.playlist.RemotePlaylistData
import com.toasterofbread.spmp.model.mediaitem.song.SongData
Expand Down Expand Up @@ -49,7 +48,7 @@ object LocalSongMetadataProcessor {
return item
}

suspend fun addMetadataToLocalSong(song: Song, file: PlatformFile, final_filename: String, context: AppContext) = withContext(Dispatchers.IO) {
suspend fun addMetadataToLocalSong(song: Song, file: PlatformFile, file_extension: String, context: AppContext) = withContext(Dispatchers.IO) {
val tag: Tag = Mp4Tag().apply {
fun set(key: FieldKey, value: String?) {
if (value == null) {
Expand Down Expand Up @@ -78,10 +77,7 @@ object LocalSongMetadataProcessor {
set(CUSTOM_METADATA_KEY, Json.encodeToString(custom_metadata))
}

val dot_index: Int = final_filename.lastIndexOf('.')
val extension: String = if (dot_index == -1) final_filename else final_filename.substring(dot_index + 1)

val audio_file: AudioFile = AudioFileIO.readAs(File(file.absolute_path), extension)
val audio_file: AudioFile = AudioFileIO.readAs(File(file.absolute_path), file_extension)
audio_file.tag = tag
audio_file.commit()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.toasterofbread.spmp.model.mediaitem.song.SongRef
import com.toasterofbread.spmp.platform.AppContext
import com.toasterofbread.spmp.platform.startPlatformService
import com.toasterofbread.spmp.platform.unbindPlatformService
import com.toasterofbread.spmp.ui.layout.apppage.mainpage.DownloadRequestCallback
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

Expand Down Expand Up @@ -194,21 +195,21 @@ actual class PlayerDownloadManager actual constructor(val context: AppContext) {
}

@Synchronized
actual fun startDownload(song: Song, silent: Boolean, onCompleted: ((DownloadStatus) -> Unit)?) {
actual fun startDownload(song: Song, silent: Boolean, file_uri: String?, callback: DownloadRequestCallback?) {
// If needed, get notification permission on A13 and above
if (!silent && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.application_context?.requestNotficationPermission() { granted ->
if (granted) {
performDownload(song, silent, onCompleted)
performDownload(song, silent, file_uri, callback)
}
}
}
else {
performDownload(song, silent, onCompleted)
performDownload(song, silent, file_uri, callback)
}
}

private fun performDownload(song: Song, silent: Boolean, onCompleted: ((DownloadStatus) -> Unit)?) {
private fun performDownload(song: Song, silent: Boolean, file_uri: String?, onCompleted: DownloadRequestCallback?) {
onService {
val instance: Int = result_callback_id++
if (onCompleted != null) {
Expand All @@ -222,7 +223,8 @@ actual class PlayerDownloadManager actual constructor(val context: AppContext) {
PlayerDownloadService.IntentAction.START_DOWNLOAD,
mapOf(
"song_id" to song.id,
"silent" to silent
"silent" to silent,
"file_uri" to file_uri
),
instance
)
Expand Down
5 changes: 5 additions & 0 deletions shared/src/commonMain/kotlin/PlatformTheme.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import androidx.compose.runtime.Composable
import com.toasterofbread.composekit.settings.ui.Theme

@Composable
internal expect fun PlatformTheme(theme: Theme, content: @Composable () -> Unit)
18 changes: 9 additions & 9 deletions shared/src/commonMain/kotlin/SpMp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ object SpMp {
Platform.DESKTOP -> if (!player_state.service_connected) SplashMode.SPLASH else null
}

// LoadingSplashView(
// splash_mode,
// player_state.service_loading_message,
// Modifier
// .fillMaxSize()
// .thenIf(splash_mode != null) {
// pointerInput(Unit) {}
// }
// )
LoadingSplashView(
splash_mode,
player_state.service_loading_message,
Modifier
.fillMaxSize()
.thenIf(splash_mode != null) {
pointerInput(Unit) {}
}
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.toasterofbread.composekit.platform.composable.platformClickable
import com.toasterofbread.composekit.platform.vibrateShort
import com.toasterofbread.composekit.utils.composable.OnChangedEffect
import com.toasterofbread.spmp.ui.component.longpressmenu.LongPressMenuData
import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState
import kotlinx.coroutines.delay

enum class MediaItemPreviewInteractionPressStage {
Expand Down Expand Up @@ -62,24 +63,25 @@ fun Modifier.mediaItemPreviewInteraction(
onClick: ((item: MediaItem, multiselect_key: Int?) -> Unit)? = null,
onLongClick: ((item: MediaItem, long_press_menu_data: LongPressMenuData) -> Unit)? = null
): Modifier {
val player = LocalPlayerState.current
val player: PlayerState = LocalPlayerState.current

val onClick = onClick ?: player::onMediaItemClicked
val onLongClick = onLongClick ?: player::onMediaItemLongClicked
val onItemClick = onClick ?: player::onMediaItemClicked
val onItemLongClick = onLongClick ?: player::onMediaItemLongClicked

if (Platform.DESKTOP.isCurrent()) {
return platformClickable(
onClick = { MediaItemPreviewInteractionPressStage.INSTANT.execute(item, long_press_menu_data, onClick, onLongClick) },
onAltClick = { MediaItemPreviewInteractionPressStage.LONG_1.execute(item, long_press_menu_data, onClick, onLongClick) },
onClick = { MediaItemPreviewInteractionPressStage.INSTANT.execute(item, long_press_menu_data, onItemClick, onItemLongClick) },
onAltClick = { MediaItemPreviewInteractionPressStage.LONG_1.execute(item, long_press_menu_data, onItemClick, onItemLongClick) },
onAlt2Click = { MediaItemPreviewInteractionPressStage.LONG_2.execute(item, long_press_menu_data, onItemClick, onItemLongClick) },
indication = getIndication()
)
}

var current_press_stage: MediaItemPreviewInteractionPressStage by remember { mutableStateOf(MediaItemPreviewInteractionPressStage.INSTANT) }
val long_press_timeout = LocalViewConfiguration.current.longPressTimeoutMillis
val long_press_timeout: Long = LocalViewConfiguration.current.longPressTimeoutMillis

val interaction_source = remember { MutableInteractionSource() }
val pressed by interaction_source.collectIsPressedAsState()
val interaction_source: MutableInteractionSource = remember { MutableInteractionSource() }
val pressed: Boolean by interaction_source.collectIsPressedAsState()

OnChangedEffect(pressed) {
if (pressed) {
Expand All @@ -100,7 +102,7 @@ fun Modifier.mediaItemPreviewInteraction(
player.context.vibrateShort()

if (stage == MediaItemPreviewInteractionPressStage.values().last { it.isAvailable(long_press_menu_data) }) {
current_press_stage.execute(item, long_press_menu_data, onClick, onLongClick)
current_press_stage.execute(item, long_press_menu_data, onItemClick, onItemLongClick)
long_press_menu_data.current_interaction_stage = null
break
}
Expand All @@ -109,14 +111,14 @@ fun Modifier.mediaItemPreviewInteraction(
}
else {
if (current_press_stage != MediaItemPreviewInteractionPressStage.values().last { it.isAvailable(long_press_menu_data) }) {
current_press_stage.execute(item, long_press_menu_data, onClick, onLongClick)
current_press_stage.execute(item, long_press_menu_data, onItemClick, onItemLongClick)
}
current_press_stage = MediaItemPreviewInteractionPressStage.INSTANT
long_press_menu_data.current_interaction_stage = null
}
}

return clickable(interaction_source, getIndication(), onClick = {
current_press_stage.execute(item, long_press_menu_data, onClick, onLongClick)
current_press_stage.execute(item, long_press_menu_data, onItemClick, onItemLongClick)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ data class MediaItemLayout(
content_padding: PaddingValues = PaddingValues()
) {
when (this) {
// Why
GRID -> MediaItemGrid(layout, modifier, title_modifier, grid_rows, multiselect_context = multiselect_context, apply_filter = apply_filter, square_item_max_text_rows = square_item_max_text_rows, show_download_indicators = show_download_indicators, content_padding = content_padding)
GRID_ALT -> MediaItemGrid(layout, modifier, title_modifier, grid_rows, alt_style = true, multiselect_context = multiselect_context, apply_filter = apply_filter, square_item_max_text_rows = square_item_max_text_rows, show_download_indicators = show_download_indicators, content_padding = content_padding)
ROW -> MediaItemGrid(layout, modifier, title_modifier, Pair(1, 1), multiselect_context = multiselect_context, apply_filter = apply_filter, square_item_max_text_rows = square_item_max_text_rows, show_download_indicators = show_download_indicators, content_padding = content_padding)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ object Settings {
is Long -> putLong(enum_key.getName(), value)
is String -> putString(enum_key.getName(), value)
is Set<*> -> putStringSet(enum_key.getName(), value as Set<String>)
is Enum<*> -> putInt(enum_key.getName(), value.ordinal)
else -> throw NotImplementedError("$enum_key ${value!!::class.simpleName}")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object SettingsImportExport {
val values: Map<String, Any>?
) {
fun getCategories(): List<SettingsCategory>? =
included_categories?.map { SettingsCategory.fromId(it) }
included_categories?.mapNotNull { SettingsCategory.fromIdOrNull(it) }
}

suspend fun exportSettings(
Expand Down
Loading

0 comments on commit 5edf002

Please sign in to comment.