From 62a2f42d6841568ab984606f65f4cc98b875835d Mon Sep 17 00:00:00 2001 From: austin Date: Tue, 28 Oct 2025 18:26:36 -0500 Subject: [PATCH] Fix QR code generation for large polycentric export bundles - Add GZIP compression for large export data (>2000 chars) - Implement fallback QR generation with different error correction levels - Add automatic decompression support in import functionality - Improve error handling with fallback to text display - Add localized error messages for QR code failures - Add compression ratio logging for debugging This fixes the 'Data too big' error when generating QR codes for polycentric profile exports by automatically compressing large data and providing multiple fallback mechanisms. --- .../activities/PolycentricBackupActivity.kt | 80 +++++++++++++++++-- .../PolycentricImportProfileActivity.kt | 32 +++++++- app/src/main/res/values-ar/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-zh/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 14 files changed, 117 insertions(+), 7 deletions(-) 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..34331ec9 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..0345d57b 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-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4724f6ce..269736f8 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -388,6 +388,7 @@ استثناء غير معالج في VS إرسال الاستثناء للمطورين… تم تعيين مفتاح الترخيص الخاص بك!\nقد يكون هناك حاجة لإعادة تشغيل التطبيق. + رمز الاستجابة السريعة كبير جدًا. استخدم النص أدناه لمشاركة ملفك الشخصي. تنسيق الترخيص غير صالح تنسيق المحتوى غير معروف تنسيق الملف غير معروف diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 234bb900..7cd81816 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -395,6 +395,7 @@ Unbehandelte Ausnahme in VS Ausnahme an Entwickler senden… Ihr Lizenzschlüssel wurde festgelegt!\nEin Neustart der App könnte erforderlich sein. + QR-Code zu groß. Verwenden Sie den Text unten, um Ihr Profil zu teilen. Ungültiges Lizenzformat Unbekanntes Inhaltsformat Unbekanntes Dateiformat diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 96b9449a..e9c04d20 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -372,6 +372,7 @@ Excepción no manejada en VS Enviar excepción a los desarrolladores... ¡Se ha configurado tu clave de licencia!\nPuede ser necesario reiniciar la aplicación. + Código QR demasiado grande. Use el texto de abajo para compartir su perfil. Formato de licencia no válido Formato de contenido desconocido Formato de archivo desconocido diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 557c485f..48ffdafe 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -411,6 +411,7 @@ Exception non gérée dans VS Envoyer l\'exception aux développeurs… Votre clé de licence a été définie !\nUn redémarrage de l\'application peut être nécessaire. + Code QR trop volumineux. Utilisez le texte ci-dessous pour partager votre profil. Format de licence invalide Format de contenu inconnu Format de fichier inconnu diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a5783903..d91c3b0b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -617,6 +617,7 @@ Eccezione non gestita in VS Invio eccezione agli sviluppatori… La tua chiave di licenza è stata impostata!\nIl riavvio dell\'app potrebbe essere richiesto. + Codice QR troppo grande. Usa il testo qui sotto per condividere il tuo profilo. Formato licenza non valido Formato contenuto sconosciuto Formato file sconosciuto diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e3452959..9177ffac 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -374,6 +374,7 @@ VSで未処理の例外 開発者に例外を送信… ライセンスキーが設定されました!\nアプリを再起動する可能性があります。 + QRコードが大きすぎます。下のテキストを使用してプロフィールを共有してください。 無効なライセンス形式 不明なコンテンツ形式 不明なファイル形式 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 0312da2f..01a84b8d 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -410,6 +410,7 @@ VS에서 처리되지 않은 예외 개발자에게 예외를 보냅니다… 라이선스 키가 설정되었습니다!\n앱을 다시 시작해야 할 수 있습니다. + QR 코드가 너무 큽니다. 아래 텍스트를 사용하여 프로필을 공유하세요. 잘못된 라이선스 형식 알 수 없는 콘텐츠 형식 알 수 없는 파일 형식 diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index de46d305..9a0fcec9 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -407,6 +407,7 @@ Exceção não tratada no VS Enviar exceção aos desenvolvedores… Sua chave de licença foi definida!\nUma reinicialização do aplicativo pode ser necessária. + Código QR muito grande. Use o texto abaixo para compartilhar seu perfil. Formato de licença inválido Formato de conteúdo desconhecido Formato de arquivo desconhecido diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 82a479ad..cf66c21d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -407,6 +407,7 @@ Необработанное исключение в VS Отправить исключение разработчикам… Ваш лицензионный ключ установлен!\nМожет потребоваться перезагрузка приложения. + QR-код слишком большой. Используйте текст ниже, чтобы поделиться своим профилем. Неверный формат лицензии Неизвестный формат содержимого Неизвестный формат файла diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 5a17d7b9..8cc01b87 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -581,6 +581,7 @@ VS\'de bilinmeyen hata (exception) Geliştiricilere exception\'ı gönder… Lisans anahtarınız ayarlandı!\nUygulamayı yeniden başlatmanız gerekebilir. + QR kodu çok büyük. Profilinizi paylaşmak için aşağıdaki metni kullanın. Geçersiz lisans formatı Bilinmeyen içerik formatı Bilinmeyen dosya formatı diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 1e0e843f..74ab2f65 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -411,6 +411,7 @@ VS 中的未处理异常 向开发者发送异常… 您的许可证密钥已设置!\n可能需要重新启动应用程序。 + 二维码太大。请使用下方文字分享您的个人资料。 无效的许可证格式 未知的内容格式 未知的文件格式 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 92c52cf4..7f5a1f7c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -643,6 +643,7 @@ Unhandled exception in VS Send exception to developers… Your license key has been set!\nAn app restart might be required. + QR code too large. Use the text below to share your profile. Invalid license format Unknown content format Unknown file format