From 41fbb204bbfdab2a2911783202a1c955d2bdd97d Mon Sep 17 00:00:00 2001 From: Hasinjato Date: Fri, 7 Aug 2026 15:43:56 +0300 Subject: [PATCH] Support share button on FAB : export pdf|mid - desktop&android --- .../mg/dot/feufaro/AndroidFileRepository.kt | 27 +++--- .../dot/feufaro/midi/MidiWriterforEXport.kt | 13 ++- .../kotlin/mg/dot/feufaro/solfa/Solfa.kt | 12 ++- .../kotlin/mg/dot/feufaro/ui/DrawerUI.kt | 84 ++++++++++++++----- .../dot/feufaro/viewmodel/SolfaScreenModel.kt | 4 +- 5 files changed, 98 insertions(+), 42 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidFileRepository.kt b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidFileRepository.kt index 416dfbe..dc6abee 100644 --- a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidFileRepository.kt +++ b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/AndroidFileRepository.kt @@ -80,12 +80,6 @@ class AndroidFileRepository(private val context: Context) : FileRepository { val folder = context.getExternalFilesDir(null) return "$folder/$shortName" } - /*override suspend fun readFileBytes(filePath: String): ByteArray = withContext(Dispatchers.IO) { - val folder = context.getExternalFilesDir(null) - val file = File(folder, filePath) - if (!file.exists()) throw IOException("File not found: ${file.absolutePath}") - file.readBytes() - }*/ private fun String.parseSafToRelativePath(): String { if (!this.startsWith("content://")) return this @@ -129,10 +123,12 @@ class AndroidFileRepository(private val context: Context) : FileRepository { if (filePath.endsWith(".pdf", ignoreCase = true)) { openPdfFile(context = context, filePath = filePath) } else { + val shortDisplayPath = longPathToShort(displayPath) + Toast.makeText( context.applicationContext, - "Sauvegarde réussie : ${displayPath.substringAfterLast('/')}", - Toast.LENGTH_LONG + "Sauvegarde réussie dans : $shortDisplayPath", + Toast.LENGTH_LONG ).show() } } @@ -144,6 +140,15 @@ class AndroidFileRepository(private val context: Context) : FileRepository { throw e } } + + private fun longPathToShort(displayPath: String): String { + return if (displayPath.contains("Documents/")) { + "Documents/" + displayPath.substringAfter("Documents/") + } else { + displayPath.substringAfterLast('/') + } + } + override suspend fun getAppPublicFolder(): File { val publicDocsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) val appDir = File(publicDocsDir, "Feufaro") @@ -162,7 +167,8 @@ class AndroidFileRepository(private val context: Context) : FileRepository { } override fun pickSavePath(defaultName: String, title: String): String? { - val file = File(context.filesDir, defaultName) + val publicFolder = getPAppPublicFolder() + val file = File(publicFolder, defaultName) return file?.absolutePath } suspend fun openPdfFile(context: Context, filePath: String) { @@ -182,9 +188,10 @@ class AndroidFileRepository(private val context: Context) : FileRepository { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } + val sPath = longPathToShort(filePath) try { withContext(Dispatchers.Main) { - Toast.makeText(context.applicationContext, "Fichier sauvegardé avec succès", Toast.LENGTH_LONG).show() + Toast.makeText(context.applicationContext, "Fichier sauvegardé avec succès dans $sPath", Toast.LENGTH_LONG).show() } context.startActivity(intent) } catch (e: Exception) { diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterforEXport.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterforEXport.kt index f98c1fe..36a1427 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterforEXport.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterforEXport.kt @@ -3,6 +3,7 @@ package mg.dot.feufaro.midi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import mg.dot.feufaro.FileRepository import mg.dot.feufaro.solfa.Transpose import mg.dot.feufaro.viewmodel.MidiMarkers @@ -37,16 +38,12 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor } lastPitch[voiceNumber] = midiNote } - fun save(filePath: String) { - val parseScope = CoroutineScope(Dispatchers.Default) - parseScope.launch { - sequence.write() - fileRepository.saveFile(filePath, sequence.out.toByteArray()) - //val fout = fileRepository.getOutputStream(filePath) - } + suspend fun save(filePath: String) = withContext(Dispatchers.Default) { + sequence.write() + fileRepository.saveLocalFile(filePath, sequence.out.toByteArray()) } fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaBytes: ByteArray) { - sequence.addMeta(type, tick, nbData, metaBytes.toString()) + sequence.addMetaFe(type, tick, nbData, metaBytes) } fun setTempoMeta(bpm: Float, tickPosition: Int = 0) { diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt index f8e6230..dcb8447 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt @@ -261,7 +261,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository private val prefs: Settings = provideSettings() - fun generateMidiFile(bpm: Float) { + fun generateMidiFile(bpm: Float, outputPath: String = "whawyd3.mid") { val parseScope = CoroutineScope(Dispatchers.Default) parseScope.launch { val pitchesSorted = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber })) @@ -273,8 +273,14 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository 3 to prefs.getInt("voice_instrument", 0), 4 to prefs.getInt("voice_instrument", 0), ) - midiWriter.process(pitchesSorted, sharedScreenModel.getFullMarkers(), initialBpm = bpm, instruments, initialKey = sharedScreenModel.songKey.value) - midiWriter.save("whawyd3.mid") + midiWriter.process( + pitchesSorted, + sharedScreenModel.getFullMarkers(), + initialBpm = bpm, + instruments, + initialKey = sharedScreenModel.songKey.value + ) + midiWriter.save(outputPath) } } fun justBuild(sourceFile: String, sourceContent: String) { diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt index 2bd64ba..89540d8 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt @@ -88,6 +88,7 @@ fun MainScreenWithDrawer( val isExpanded by sharedScreenModel.expandedFAB.collectAsState() val showMidiCtrl by sharedScreenModel.showMidiCtrl.collectAsState() + var isShareExpanded by remember { mutableStateOf(false) } val isPlay by sharedScreenModel.isPlay.collectAsState() val isPos by sharedScreenModel.isPos.collectAsState() @@ -732,27 +733,72 @@ fun MainScreenWithDrawer( enter = fadeIn() + scaleIn() + slideInVertically { it / 2 }, exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 } ) { - MyFAB( - onClick = { - sharedScreenModel.toggleQRCodeVisibility() - sharedScreenModel.setExpandedFAB(false) - }, - icon = Icons.Filled.QrCode - ) - } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + AnimatedVisibility( + visible = isShareExpanded, + enter = fadeIn() + expandHorizontally(), + exit = fadeOut() + shrinkHorizontally() + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + MyFAB( + onClick = { + sharedScreenModel.toggleQRCodeVisibility() + sharedScreenModel.setExpandedFAB(false) + isShareExpanded = false + }, + icon = Icons.Filled.QrCode2 + ) + MyFAB( + onClick = { + showPrintSettings = !showPrintSettings + isShareExpanded = false + }, + icon = Icons.Filled.Print + ) - AnimatedVisibility( - visible = isExpanded and !showMidiCtrl, - enter = fadeIn() + scaleIn() + slideInVertically { it / 2 }, - exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 } - ) { - MyFAB( - onClick = { - showPrintSettings = !showPrintSettings - }, - icon = Icons.Filled.Print - ) + MyFAB( + onClick = { + isShareExpanded = false + scope.launch { + withContext(Dispatchers.IO) { + try { + val cleanS = songTitle.replace(' ','_') + val exportName = "${cleanS.ifEmpty { "partition" }}.mid" + + val savedPath = solfaScreenModel.fileRepository.pickSavePath( + defaultName = exportName, + title = "Enregistrer le fichier MIDI" + ) + + if (!savedPath.isNullOrEmpty()) { + val bpm = sharedScreenModel.getBpmFlow().value ?: 120f + + solfaScreenModel.generateMidiFile(bpm, savedPath) + println("Fichier MIDI enregistré avec succès sous : $savedPath") + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + }, + icon = Icons.Filled.AudioFile + ) + } + } + + MyFAB( + onClick = { isShareExpanded = !isShareExpanded }, + icon = Icons.Filled.Share + ) + } } AnimatedVisibility( diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt index 82d74d3..5c3723a 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt @@ -55,7 +55,7 @@ class SolfaScreenModel( solfa.createNewSolfa(metadata) } } - fun regenMidi(bpm: Float) { - solfa.generateMidiFile(bpm) + fun generateMidiFile(bpm: Float, path: String) { + solfa.generateMidiFile(bpm, path) } } \ No newline at end of file