diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt index 9cf58134..b2375287 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt @@ -29,14 +29,19 @@ import com.futo.polycentric.core.StorageTypeCRDTSetItem import com.futo.polycentric.core.Store import com.futo.polycentric.core.toBase64Url import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType import com.google.zxing.MultiFormatWriter import com.google.zxing.common.BitMatrix +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import userpackage.Protocol import userpackage.Protocol.ExportBundle import userpackage.Protocol.URLInfo +import java.io.ByteArrayOutputStream +import java.util.zip.GZIPOutputStream +import android.util.Base64 class PolycentricBackupActivity : AppCompatActivity() { private lateinit var _buttonShare: BigButton; @@ -74,6 +79,8 @@ class PolycentricBackupActivity : AppCompatActivity() { try { val pair = withContext(Dispatchers.IO) { val bundle = createExportBundle() + Logger.i(TAG, "Export bundle created, length: ${bundle.length}") + val dimension = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics ).toInt() @@ -89,10 +96,22 @@ class PolycentricBackupActivity : AppCompatActivity() { _buttonCopy.visibility = View.VISIBLE } catch (e: Exception) { Logger.e(TAG, getString(R.string.failed_to_generate_qr_code), e) + + // Show the export bundle text even if QR code generation fails + _exportBundle = withContext(Dispatchers.IO) { createExportBundle() } + + // Provide more specific error message based on the exception + val errorMessage = when { + e.message?.contains("Data too big") == true -> getString(R.string.qr_code_too_large_use_text_below) + else -> getString(R.string.failed_to_generate_qr_code) + } + _textQR.text = errorMessage + _textQR.visibility = View.VISIBLE + _buttonShare.visibility = View.VISIBLE + _buttonCopy.visibility = View.VISIBLE + + // Hide QR image since generation failed _imageQR.visibility = View.INVISIBLE - _textQR.visibility = View.INVISIBLE - _buttonShare.visibility = View.INVISIBLE - _buttonCopy.visibility = View.INVISIBLE } finally { _loader.visibility = View.GONE } @@ -111,8 +130,33 @@ class PolycentricBackupActivity : AppCompatActivity() { } private fun generateQRCode(content: String, width: Int, height: Int): Bitmap { - val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height); - return bitMatrixToBitmap(bitMatrix); + // Try different error correction levels and settings to handle large data + val errorCorrectionLevels = listOf( + ErrorCorrectionLevel.L, // 7% recovery + ErrorCorrectionLevel.M, // 15% recovery + ErrorCorrectionLevel.Q, // 25% recovery + ErrorCorrectionLevel.H // 30% recovery + ) + + var lastException: Exception? = null + + for (errorLevel in errorCorrectionLevels) { + try { + val hints = java.util.EnumMap(EncodeHintType::class.java) + hints[EncodeHintType.ERROR_CORRECTION] = errorLevel + hints[EncodeHintType.MARGIN] = 1 + + val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints) + return bitMatrixToBitmap(bitMatrix) + } catch (e: Exception) { + lastException = e + Logger.w(TAG, "Failed to generate QR code with error correction level $errorLevel: ${e.message}") + continue + } + } + + // If all attempts fail, throw the last exception + throw lastException ?: Exception("Failed to generate QR code") } private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap { @@ -203,7 +247,31 @@ class PolycentricBackupActivity : AppCompatActivity() { .setBody(exportBundle.toByteString()) .build(); - return "polycentric://" + urlInfo.toByteArray().toBase64Url() + val originalData = urlInfo.toByteArray() + val originalUrl = "polycentric://" + originalData.toBase64Url() + + // If the original URL is too long, try compression + if (originalUrl.length > 2000) { // QR code practical limit + try { + val compressedData = compressData(originalData) + val compressedUrl = "polycentric://" + compressedData.toBase64Url() + val compressionRatio = (compressedUrl.length.toFloat() / originalUrl.length * 100).toInt() + Logger.i(TAG, "Using compressed export bundle. Original size: ${originalUrl.length}, Compressed size: ${compressedUrl.length}, Compression ratio: ${compressionRatio}%") + return compressedUrl + } catch (e: Exception) { + Logger.w(TAG, "Failed to compress export bundle, using original", e) + } + } + + return originalUrl + } + + private fun compressData(data: ByteArray): ByteArray { + val outputStream = ByteArrayOutputStream() + GZIPOutputStream(outputStream).use { gzip -> + gzip.write(data) + } + return outputStream.toByteArray() } companion object { diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt index ab6d70a3..61af4631 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import userpackage.Protocol import userpackage.Protocol.ExportBundle +import java.io.ByteArrayInputStream +import java.util.zip.GZIPInputStream class PolycentricImportProfileActivity : AppCompatActivity() { private lateinit var _buttonHelp: ImageButton; @@ -108,7 +110,20 @@ class PolycentricImportProfileActivity : AppCompatActivity() { lifecycleScope.launch(Dispatchers.IO) { try { val data = url.substring("polycentric://".length).base64UrlToByteArray(); - val urlInfo = Protocol.URLInfo.parseFrom(data); + + // Try to parse as regular data first, if it fails, try decompressing + val urlInfo = try { + Protocol.URLInfo.parseFrom(data) + } catch (e: Exception) { + // If parsing fails, try to decompress the data + try { + val decompressedData = decompressData(data) + Protocol.URLInfo.parseFrom(decompressedData) + } catch (decompressException: Exception) { + throw Exception("Failed to parse URL data: ${e.message}") + } + } + if (urlInfo.urlType != 3L) { throw Exception("Expected urlInfo struct of type ExportBundle") } @@ -163,6 +178,21 @@ class PolycentricImportProfileActivity : AppCompatActivity() { } } + private fun decompressData(data: ByteArray): ByteArray { + val inputStream = ByteArrayInputStream(data) + val outputStream = java.io.ByteArrayOutputStream() + + GZIPInputStream(inputStream).use { gzip -> + val buffer = ByteArray(8192) // 8KB buffer + var bytesRead: Int + while (gzip.read(buffer).also { bytesRead = it } != -1) { + outputStream.write(buffer, 0, bytesRead) + } + } + + return outputStream.toByteArray() + } + companion object { private const val TAG = "PolycentricImportProfileActivity"; } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 51911585..8bca2053 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -404,6 +404,7 @@ Unbekannter Rekonstruktionstyp Fehler beim Parsen von NewPipe-Abonnements Fehler beim Generieren des QR-Codes + QR-Code zu groß. Verwenden Sie den Text unten, um Ihr Profil zu teilen. Text teilen Text kopiert Muss mindestens 3 Zeichen lang sein. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 43461707..a0a9c153 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -381,6 +381,7 @@ Tipo de reconstrucción desconocido Error al analizar las suscripciones de NewPipe Error al generar el código QR + Código QR demasiado grande. Use el texto de abajo para compartir su perfil. Compartir texto Texto copiado Debe tener al menos 3 caracteres de longitud. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 438020da..b02636cf 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -420,6 +420,7 @@ Type de reconstruction inconnu Échec de l\'analyse des abonnements NewPipe Échec de la génération du code QR + Code QR trop volumineux. Utilisez le texte ci-dessous pour partager votre profil. Partager le texte Texte copié Doit comporter au moins 3 caractères. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5ddeef06..9bad490e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -642,6 +642,7 @@ Failed to parse text file Failed to parse NewPipe Subscriptions Failed to generate QR code + QR code too large. Use the text below to share your profile. Share Text Copied Text Must be at least 3 characters long.