Compare commits
No commits in common. "49186a76c8a39f38dbea87219c6d3023a9dbc1f7" and "a196ed4322dcf73601e053e6d64437bcc0128485" have entirely different histories.
49186a76c8
...
a196ed4322
10 changed files with 56 additions and 246 deletions
|
|
@ -15,8 +15,7 @@
|
|||
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".MainActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
|
|
|||
|
|
@ -85,39 +85,22 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
|
|||
file.readBytes()
|
||||
}*/
|
||||
override suspend fun saveLocalFile(filePath: String, data: ByteArray) = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
if (filePath.startsWith("content://") || filePath.startsWith("file://")) {
|
||||
val uri = Uri.parse(filePath)
|
||||
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||
outputStream.write(data)
|
||||
outputStream.flush()
|
||||
} ?: throw Exception("Impossible d'ouvrir l'Uri pour écriture")
|
||||
} else {
|
||||
val file = if (filePath.contains("/")) {
|
||||
File(filePath)
|
||||
} else {
|
||||
File(getPAppPublicFolder(), filePath)
|
||||
}
|
||||
file.parentFile?.mkdirs()
|
||||
file.writeBytes(data)
|
||||
}
|
||||
val file = if (filePath.contains("/") ) {
|
||||
File(filePath)
|
||||
} else {
|
||||
File(getPAppPublicFolder(), filePath)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (filePath.endsWith(".pdf", ignoreCase = true)) {
|
||||
openPdfFile(context = context, filePath = filePath)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
context.applicationContext,
|
||||
"Sauvegarde réussie",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
try {
|
||||
file.parentFile?.mkdirs()
|
||||
file.writeBytes(data)
|
||||
println("Fichier sauvegardé dans : ${file.absolutePath}")
|
||||
|
||||
if (file.extension.lowercase() == "pdf") {
|
||||
openPdfFile(context = context, filePath = file.absolutePath)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Erreur de sauvegarde : ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
|
@ -138,9 +121,8 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
|
|||
return appDir
|
||||
}
|
||||
|
||||
override fun pickSavePath(defaultName: String, title: String): String? {
|
||||
val file = File(context.filesDir, defaultName)
|
||||
return file?.absolutePath
|
||||
override fun pickSavePath(defaultName: String): String? {
|
||||
return File(getPAppPublicFolder(), defaultName).absolutePath
|
||||
}
|
||||
suspend fun openPdfFile(context: Context, filePath: String) {
|
||||
val file = File(filePath)
|
||||
|
|
|
|||
|
|
@ -89,10 +89,12 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
println("DeepLink: rawContent = $rawContent")
|
||||
|
||||
val fileName = data.getQueryParameter("name") ?: run {
|
||||
println("DeepLink: paramètre 'name' introuvable")
|
||||
return
|
||||
}
|
||||
val fileName = rawContent
|
||||
.split("|")
|
||||
.firstOrNull { it.startsWith("t:") }
|
||||
?.removePrefix("t:")
|
||||
?.trim()
|
||||
|
||||
println("DeepLink: fileName extrait = $fileName")
|
||||
|
||||
if (fileName.isNullOrBlank()) {
|
||||
|
|
@ -109,7 +111,7 @@ class MainActivity : ComponentActivity() {
|
|||
appDir.mkdirs()
|
||||
// docsDir.mkdirs()
|
||||
|
||||
val file = File(appDir, fileName)
|
||||
val file = File(appDir, "$fileName.txt")
|
||||
|
||||
try {
|
||||
file.writeText(rawContent)
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package mg.dot.feufaro.ui
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
||||
@Composable
|
||||
actual fun rememberFileSaveLauncher(
|
||||
onResult: (String?) -> Unit
|
||||
): FileSaveLauncher {
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("text/plain")
|
||||
) { uri ->
|
||||
onResult(uri?.toString())
|
||||
}
|
||||
|
||||
return remember {
|
||||
object : FileSaveLauncher {
|
||||
override fun launch(defaultName: String) {
|
||||
launcher.launch(defaultName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ interface FileRepository {
|
|||
suspend fun getAppPublicFolder() : File
|
||||
fun getFileName(shortName: String) : String
|
||||
// suspend fun readFileBytes(filePath: String): ByteArray
|
||||
fun pickSavePath(defaultName: String, title: String): String?
|
||||
fun pickSavePath(defaultName: String): String?
|
||||
}
|
||||
|
||||
// This is just a regular class that implements the common 'FileRepository' interface.
|
||||
|
|
|
|||
|
|
@ -145,10 +145,9 @@ LaunchedEffect(isPlay, isPos) {
|
|||
)
|
||||
}
|
||||
val favoritePaths by sharedScreenModel.playlistItems.collectAsState()
|
||||
Scaffold(contentWindowInsets = WindowInsets.safeDrawing, topBar = {
|
||||
Scaffold(contentWindowInsets = WindowInsets(0, 0, 0, 0), topBar = {
|
||||
TopAppBar(
|
||||
modifier = Modifier.height(55.dp).windowInsetsPadding(WindowInsets.safeDrawing),
|
||||
title = {
|
||||
modifier = Modifier.height(55.dp).windowInsetsPadding(WindowInsets.statusBars), title = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().verticalScroll(scrollState),
|
||||
verticalArrangement = Arrangement.Center
|
||||
|
|
@ -170,17 +169,13 @@ LaunchedEffect(isPlay, isPos) {
|
|||
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
|
||||
}
|
||||
}, actions = {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = songKey,
|
||||
fontSize = 25.sp,
|
||||
fontWeight = FontWeight.Black,
|
||||
modifier = Modifier.padding(end = 16.dp)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = songKey,
|
||||
fontSize = 25.sp,
|
||||
fontWeight = FontWeight.Black,
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
|
||||
}, colors = TopAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
|
|
@ -233,24 +228,6 @@ LaunchedEffect(isPlay, isPos) {
|
|||
|
||||
|
||||
}
|
||||
/*AnimatedVisibility(
|
||||
visible = isExpanded and !showMidiCtrl,
|
||||
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
sharedScreenModel.toggleEditMode()
|
||||
sharedScreenModel.setExpandedFAB(false)
|
||||
}, modifier = Modifier.alpha(0.45f)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Edit,
|
||||
contentDescription = "null",
|
||||
tint = Color.Blue
|
||||
)
|
||||
}
|
||||
}*/
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded and !showMidiCtrl,
|
||||
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||
|
|
@ -368,14 +345,11 @@ LaunchedEffect(isPlay, isPos) {
|
|||
}) { paddingValues ->
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(paddingValues).consumeWindowInsets(paddingValues).windowInsetsPadding(WindowInsets.ime)
|
||||
modifier = Modifier.fillMaxSize().padding(paddingValues).windowInsetsPadding(WindowInsets.ime)
|
||||
) {
|
||||
content(PaddingValues(0.dp))
|
||||
if (sharedScreenModel.isQRCodeVisible.value) {
|
||||
QRDisplay(
|
||||
sharedScreenModel = sharedScreenModel,
|
||||
fileRepository = solfaScreenModel.fileRepository
|
||||
)
|
||||
QRDisplay(sharedScreenModel = sharedScreenModel)
|
||||
} else {
|
||||
if (filteredSongs.isNotEmpty()) {
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
package mg.dot.feufaro.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
interface FileSaveLauncher {
|
||||
fun launch(defaultName: String)
|
||||
}
|
||||
|
||||
@Composable
|
||||
expect fun rememberFileSaveLauncher(
|
||||
onResult: (String?) -> Unit
|
||||
): FileSaveLauncher
|
||||
|
|
@ -6,48 +6,42 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import mg.dot.feufaro.FileRepository
|
||||
import mg.dot.feufaro.solfa.ColorPrefixA
|
||||
import mg.dot.feufaro.solfa.ColorPrefixB
|
||||
import mg.dot.feufaro.solfa.ColorValue
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.URLEncoder
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import kotlin.io.encoding.Base64
|
||||
|
||||
@Composable
|
||||
fun QRDisplay(sharedScreenModel: SharedScreenModel, fileRepository: FileRepository) {
|
||||
fun QRDisplay(sharedScreenModel: SharedScreenModel) {
|
||||
|
||||
val content by sharedScreenModel.fileContent
|
||||
val path by sharedScreenModel.activeFilePath
|
||||
val fileName = path.substringAfterLast('/').substringAfterLast(':')
|
||||
|
||||
val qrCodeImage by produceState<ImageBitmap?>(initialValue = null, content, path) {
|
||||
val expandedSource = expandInclusions(
|
||||
content = content,
|
||||
currentFilePath = path,
|
||||
fileRepository = fileRepository
|
||||
)
|
||||
if(expandedSource.isNotEmpty()) {
|
||||
val bos = ByteArrayOutputStream()
|
||||
GZIPOutputStream(bos).use { it.write(expandedSource.toByteArray(Charsets.UTF_8)) }
|
||||
val compressedBytes = bos.toByteArray()
|
||||
fun compressAndEncode(content: String?): String {
|
||||
val bos = ByteArrayOutputStream()
|
||||
GZIPOutputStream(bos).use { it.write(content?.toByteArray(Charsets.UTF_8)) }
|
||||
val compressedBytes = bos.toByteArray()
|
||||
return Base64.UrlSafe.encode(compressedBytes).trim('=')
|
||||
}
|
||||
|
||||
val compressedData = Base64.UrlSafe.encode(compressedBytes).trim('=')
|
||||
val compressedData = compressAndEncode(content)
|
||||
val uri = "feufaro://song?file=$compressedData"
|
||||
// val encodedContent = URLEncoder.encode(content, "UTF-8")
|
||||
// val uri = "feufaro://song?file=$encodedContent"
|
||||
|
||||
val uri = "feufaro://song?file=${compressedData}&name=$fileName"
|
||||
value = generateQRCode(uri, size = 800)
|
||||
val qrCodeImage = remember(content) {
|
||||
if (content != null) {
|
||||
generateQRCode(uri, size = 800)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,79 +64,14 @@ fun QRDisplay(sharedScreenModel: SharedScreenModel, fileRepository: FileReposito
|
|||
Text("Scanner pour ouvrir ce partition")
|
||||
if (qrCodeImage != null) {
|
||||
Image(
|
||||
bitmap = qrCodeImage!!,
|
||||
bitmap = qrCodeImage,
|
||||
contentDescription = "Code QR du fichier actif",
|
||||
modifier = Modifier.size(400.dp)
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(400.dp)
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(48.dp),
|
||||
color = ColorPrefixA,
|
||||
strokeWidth = 4.dp,
|
||||
trackColor = ColorPrefixB.copy(alpha = 0.2f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = "Génération du QR Code...",
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = ColorValue,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
Text("Chargement du Code QR ou contenu vide...", modifier = Modifier.padding(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun expandInclusions(
|
||||
content: String?,
|
||||
currentFilePath: String,
|
||||
fileRepository: FileRepository
|
||||
): String {
|
||||
if(content.isNullOrEmpty()) return ""
|
||||
val lines = content.split("\n")
|
||||
val result = StringBuilder()
|
||||
val lastSlash = currentFilePath.lastIndexOf('/')
|
||||
val directory = if (lastSlash != -1) currentFilePath.substring(0, lastSlash + 1) else ""
|
||||
|
||||
lines.forEach { line ->
|
||||
if (line.startsWith("I0:")) {
|
||||
try {
|
||||
val parts = line.substring(3).split(":")
|
||||
val fileName = parts[0]
|
||||
val ignorePattern = if (parts.size > 1) parts[1] else ""
|
||||
|
||||
val fullPath = directory + fileName
|
||||
val includedLines = fileRepository.readFileLines(fullPath)
|
||||
|
||||
val regexIgnore = if (ignorePattern.isNotEmpty()) Regex(ignorePattern) else null
|
||||
|
||||
includedLines.forEach { incLine ->
|
||||
if (regexIgnore == null || !regexIgnore.containsMatchIn(incLine)) {
|
||||
result.append(incLine).append("\n")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
result.append("// Erreur inclusion: ${e.message}\n")
|
||||
}
|
||||
} else {
|
||||
result.append(line).append("\n")
|
||||
}
|
||||
}
|
||||
return result.toString().trimEnd()
|
||||
}
|
||||
|
|
@ -72,8 +72,8 @@ class DesktopFileRepository : FileRepository { // IMPORTS AND IMPLEMENTS THE com
|
|||
return "$userHome/$shortName"
|
||||
}
|
||||
|
||||
override fun pickSavePath(defaultName: String, title: String): String? {
|
||||
val dialog = FileDialog(null as Frame?, title, FileDialog.SAVE)
|
||||
override fun pickSavePath(defaultName: String): String? {
|
||||
val dialog = FileDialog(null as Frame?, "Exporter en PDF", FileDialog.SAVE)
|
||||
dialog.directory = System.getProperty("user.home")
|
||||
dialog.file = defaultName
|
||||
dialog.isVisible = true
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
package mg.dot.feufaro.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.awt.FileDialog
|
||||
import java.awt.Frame
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
actual fun rememberFileSaveLauncher(
|
||||
onResult: (String?) -> Unit
|
||||
): FileSaveLauncher {
|
||||
return remember {
|
||||
object : FileSaveLauncher {
|
||||
override fun launch(defaultName: String) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val dialog = FileDialog(null as Frame?, "Sauvegarder la source", FileDialog.SAVE)
|
||||
dialog.directory = System.getProperty("user.home")
|
||||
dialog.file = defaultName
|
||||
dialog.isVisible = true
|
||||
|
||||
val path = if (dialog.directory != null && dialog.file != null) {
|
||||
File(dialog.directory, dialog.file).absolutePath
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
onResult(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue