Support share button on FAB : export pdf|mid - desktop&android

This commit is contained in:
Hasinjato 2026-08-07 15:43:56 +03:00
parent 92a3a0344d
commit 41fbb204bb
5 changed files with 98 additions and 42 deletions

View file

@ -80,12 +80,6 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
val folder = context.getExternalFilesDir(null) val folder = context.getExternalFilesDir(null)
return "$folder/$shortName" 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 { private fun String.parseSafToRelativePath(): String {
if (!this.startsWith("content://")) return this if (!this.startsWith("content://")) return this
@ -129,9 +123,11 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
if (filePath.endsWith(".pdf", ignoreCase = true)) { if (filePath.endsWith(".pdf", ignoreCase = true)) {
openPdfFile(context = context, filePath = filePath) openPdfFile(context = context, filePath = filePath)
} else { } else {
val shortDisplayPath = longPathToShort(displayPath)
Toast.makeText( Toast.makeText(
context.applicationContext, context.applicationContext,
"Sauvegarde réussie : ${displayPath.substringAfterLast('/')}", "Sauvegarde réussie dans : $shortDisplayPath",
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show() ).show()
} }
@ -144,6 +140,15 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
throw e 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 { override suspend fun getAppPublicFolder(): File {
val publicDocsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) val publicDocsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
val appDir = File(publicDocsDir, "Feufaro") val appDir = File(publicDocsDir, "Feufaro")
@ -162,7 +167,8 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
} }
override fun pickSavePath(defaultName: String, title: String): String? { 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 return file?.absolutePath
} }
suspend fun openPdfFile(context: Context, filePath: String) { suspend fun openPdfFile(context: Context, filePath: String) {
@ -182,9 +188,10 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
} }
val sPath = longPathToShort(filePath)
try { try {
withContext(Dispatchers.Main) { 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) context.startActivity(intent)
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -3,6 +3,7 @@ package mg.dot.feufaro.midi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.solfa.Transpose import mg.dot.feufaro.solfa.Transpose
import mg.dot.feufaro.viewmodel.MidiMarkers import mg.dot.feufaro.viewmodel.MidiMarkers
@ -37,16 +38,12 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
} }
lastPitch[voiceNumber] = midiNote lastPitch[voiceNumber] = midiNote
} }
fun save(filePath: String) { suspend fun save(filePath: String) = withContext(Dispatchers.Default) {
val parseScope = CoroutineScope(Dispatchers.Default)
parseScope.launch {
sequence.write() sequence.write()
fileRepository.saveFile(filePath, sequence.out.toByteArray()) fileRepository.saveLocalFile(filePath, sequence.out.toByteArray())
//val fout = fileRepository.getOutputStream(filePath)
}
} }
fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaBytes: ByteArray) { 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) { fun setTempoMeta(bpm: Float, tickPosition: Int = 0) {

View file

@ -261,7 +261,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
private val prefs: Settings = provideSettings() private val prefs: Settings = provideSettings()
fun generateMidiFile(bpm: Float) { fun generateMidiFile(bpm: Float, outputPath: String = "whawyd3.mid") {
val parseScope = CoroutineScope(Dispatchers.Default) val parseScope = CoroutineScope(Dispatchers.Default)
parseScope.launch { parseScope.launch {
val pitchesSorted = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber })) 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), 3 to prefs.getInt("voice_instrument", 0),
4 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.process(
midiWriter.save("whawyd3.mid") pitchesSorted,
sharedScreenModel.getFullMarkers(),
initialBpm = bpm,
instruments,
initialKey = sharedScreenModel.songKey.value
)
midiWriter.save(outputPath)
} }
} }
fun justBuild(sourceFile: String, sourceContent: String) { fun justBuild(sourceFile: String, sourceContent: String) {

View file

@ -88,6 +88,7 @@ fun MainScreenWithDrawer(
val isExpanded by sharedScreenModel.expandedFAB.collectAsState() val isExpanded by sharedScreenModel.expandedFAB.collectAsState()
val showMidiCtrl by sharedScreenModel.showMidiCtrl.collectAsState() val showMidiCtrl by sharedScreenModel.showMidiCtrl.collectAsState()
var isShareExpanded by remember { mutableStateOf(false) }
val isPlay by sharedScreenModel.isPlay.collectAsState() val isPlay by sharedScreenModel.isPlay.collectAsState()
val isPos by sharedScreenModel.isPos.collectAsState() val isPos by sharedScreenModel.isPos.collectAsState()
@ -731,28 +732,73 @@ fun MainScreenWithDrawer(
visible = isExpanded and !showMidiCtrl, visible = isExpanded and !showMidiCtrl,
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 }, enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 } exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
) {
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( MyFAB(
onClick = { onClick = {
sharedScreenModel.toggleQRCodeVisibility() sharedScreenModel.toggleQRCodeVisibility()
sharedScreenModel.setExpandedFAB(false) sharedScreenModel.setExpandedFAB(false)
isShareExpanded = false
}, },
icon = Icons.Filled.QrCode icon = Icons.Filled.QrCode2
) )
}
AnimatedVisibility(
visible = isExpanded and !showMidiCtrl,
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
) {
MyFAB( MyFAB(
onClick = { onClick = {
showPrintSettings = !showPrintSettings showPrintSettings = !showPrintSettings
isShareExpanded = false
}, },
icon = Icons.Filled.Print 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( AnimatedVisibility(

View file

@ -55,7 +55,7 @@ class SolfaScreenModel(
solfa.createNewSolfa(metadata) solfa.createNewSolfa(metadata)
} }
} }
fun regenMidi(bpm: Float) { fun generateMidiFile(bpm: Float, path: String) {
solfa.generateMidiFile(bpm) solfa.generateMidiFile(bpm, path)
} }
} }