diff --git a/app/aar/ffmpeg-kit-full-6.0-2.LTS.aar b/app/aar/ffmpeg-kit-full-6.0-2.LTS.aar deleted file mode 100644 index 27b62b35..00000000 --- a/app/aar/ffmpeg-kit-full-6.0-2.LTS.aar +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea10d3c5562c9f449a4e89e9c3dfcf881ed79a952f3409bc005bcc62c2cf4b81 -size 65512557 diff --git a/app/build.gradle b/app/build.gradle index 25d458d4..06b763ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -119,7 +119,7 @@ android { buildTypes { release { - signingConfig signingConfigs.release + signingConfig signingConfigs.debug minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -231,4 +231,6 @@ dependencies { testImplementation "org.mockito:mockito-core:5.4.0" androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + //ffmpeg + implementation files('libs/ffmpeg-kit-full-6.0-2.aar') } diff --git a/app/libs/ffmpeg-kit-full-6.0-2.aar b/app/libs/ffmpeg-kit-full-6.0-2.aar new file mode 100644 index 00000000..6062734a Binary files /dev/null and b/app/libs/ffmpeg-kit-full-6.0-2.aar differ 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 d10bf731..7c15d7ea 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt @@ -14,8 +14,10 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.FileProvider import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp @@ -40,13 +42,13 @@ 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 +import java.io.File +import java.io.FileWriter class PolycentricBackupActivity : AppCompatActivity() { private lateinit var _buttonShare: BigButton; private lateinit var _buttonCopy: BigButton; + private lateinit var _buttonExportFile: BigButton; private lateinit var _imageQR: ImageView; private lateinit var _exportBundle: String; private lateinit var _textQR: TextView; @@ -64,6 +66,7 @@ class PolycentricBackupActivity : AppCompatActivity() { _buttonShare = findViewById(R.id.button_share) _buttonCopy = findViewById(R.id.button_copy) + _buttonExportFile = findViewById(R.id.button_export_file) _imageQR = findViewById(R.id.image_qr) _textQR = findViewById(R.id.text_qr) _textQRHint = findViewById(R.id.text_qr_hint) @@ -78,6 +81,7 @@ class PolycentricBackupActivity : AppCompatActivity() { _loader.visibility = View.VISIBLE _buttonShare.visibility = View.INVISIBLE _buttonCopy.visibility = View.INVISIBLE + _buttonExportFile.visibility = View.INVISIBLE lifecycleScope.launch { try { @@ -111,12 +115,14 @@ class PolycentricBackupActivity : AppCompatActivity() { // 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) + // Show file export button when QR code is too large + if (e.message?.contains("Data too big") == true) { + _textQR.text = getString(R.string.qr_code_too_large_use_file_export) + _buttonExportFile.visibility = View.VISIBLE + } else { + _textQR.text = getString(R.string.failed_to_generate_qr_code) } - _textQR.text = errorMessage + _textQR.visibility = View.VISIBLE _textQRHint.visibility = View.INVISIBLE _buttonShare.visibility = View.VISIBLE @@ -139,36 +145,19 @@ class PolycentricBackupActivity : AppCompatActivity() { val clip = ClipData.newPlainText(getString(R.string.copied_text), _exportBundle); clipboard.setPrimaryClip(clip); }; + + _buttonExportFile.onClick.subscribe { + exportToFile() + }; } private fun generateQRCode(content: String, width: Int, height: Int): Bitmap { - // 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 - ) + val hints = java.util.EnumMap(EncodeHintType::class.java) + hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.M + hints[EncodeHintType.MARGIN] = 1 - 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") + val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints) + return bitMatrixToBitmap(bitMatrix) } private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap { @@ -259,31 +248,38 @@ class PolycentricBackupActivity : AppCompatActivity() { .setBody(exportBundle.toByteString()) .build(); - 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 + val data = urlInfo.toByteArray() + return "polycentric://" + data.toBase64Url() } - - private fun compressData(data: ByteArray): ByteArray { - val outputStream = ByteArrayOutputStream() - GZIPOutputStream(outputStream).use { gzip -> - gzip.write(data) + + private fun exportToFile() { + try { + val fileName = "polycentric_profile_${System.currentTimeMillis()}.txt" + val file = File(filesDir, fileName) + + FileWriter(file).use { writer -> + writer.write(_exportBundle) + } + + val uri = FileProvider.getUriForFile( + this, + "${packageName}.fileprovider", + file + ) + + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_STREAM, uri) + putExtra(Intent.EXTRA_SUBJECT, "Polycentric Profile Export") + putExtra(Intent.EXTRA_TEXT, "Polycentric profile export file") + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + startActivity(Intent.createChooser(shareIntent, "Export Profile to File")) + } catch (e: Exception) { + Logger.e(TAG, "Failed to export to file", e) + UIDialogs.toast(this, "Failed to export profile to file") } - 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 61af4631..2456889e 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt @@ -30,8 +30,6 @@ 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; @@ -110,19 +108,7 @@ class PolycentricImportProfileActivity : AppCompatActivity() { lifecycleScope.launch(Dispatchers.IO) { try { val data = url.substring("polycentric://".length).base64UrlToByteArray(); - - // 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}") - } - } + val urlInfo = Protocol.URLInfo.parseFrom(data); if (urlInfo.urlType != 3L) { throw Exception("Expected urlInfo struct of type ExportBundle") @@ -178,21 +164,6 @@ 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/layout/activity_polycentric_backup.xml b/app/src/main/res/layout/activity_polycentric_backup.xml index 1e57365a..56cab541 100644 --- a/app/src/main/res/layout/activity_polycentric_backup.xml +++ b/app/src/main/res/layout/activity_polycentric_backup.xml @@ -90,6 +90,15 @@ app:buttonSubText="@string/copy_your_identity_to_clipboard" app:buttonIcon="@drawable/ic_copy" android:layout_marginTop="8dp" /> + + 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. + QR code too large. Use the file export button below to share your profile. Tap QR code for fullscreen view + Export to File + Save profile to file for sharing + com.futo.platformplayer.fileprovider Share Text Copied Text Must be at least 3 characters long. diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index d9e2a327..942ae5ff 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -1,5 +1,6 @@ + \ No newline at end of file diff --git a/app/src/stable/assets/sources/dailymotion b/app/src/stable/assets/sources/dailymotion index 850eb812..d1154300 160000 --- a/app/src/stable/assets/sources/dailymotion +++ b/app/src/stable/assets/sources/dailymotion @@ -1 +1 @@ -Subproject commit 850eb8122dd8348904d55ceb9c3a26b49bcb8a45 +Subproject commit d11543001150f96f3383d83fec3341d9321746b8 diff --git a/app/src/stable/assets/sources/odysee b/app/src/stable/assets/sources/odysee index 736c6b95..6ea9fa7e 160000 --- a/app/src/stable/assets/sources/odysee +++ b/app/src/stable/assets/sources/odysee @@ -1 +1 @@ -Subproject commit 736c6b953a4613145e32010ff5ee5b08be1baac6 +Subproject commit 6ea9fa7e4c20ba8c89975ac835ccebdbd1184fc4 diff --git a/app/src/stable/assets/sources/patreon b/app/src/stable/assets/sources/patreon index 6880b30b..b811f8bd 160000 --- a/app/src/stable/assets/sources/patreon +++ b/app/src/stable/assets/sources/patreon @@ -1 +1 @@ -Subproject commit 6880b30b71800f6d22ddcb692f3c1c09e745315b +Subproject commit b811f8bdfbbff73cf0d7581c9d7596911cb132b6 diff --git a/app/src/stable/assets/sources/spotify b/app/src/stable/assets/sources/spotify index 8c0f03f5..214ac1df 160000 --- a/app/src/stable/assets/sources/spotify +++ b/app/src/stable/assets/sources/spotify @@ -1 +1 @@ -Subproject commit 8c0f03f5fbc9b4e499437b85c757ec40cb7c0126 +Subproject commit 214ac1dfcc985f533d9db7d128a8315bc55fa854 diff --git a/app/src/stable/assets/sources/twitch b/app/src/stable/assets/sources/twitch index 8de3ab18..08346f91 160000 --- a/app/src/stable/assets/sources/twitch +++ b/app/src/stable/assets/sources/twitch @@ -1 +1 @@ -Subproject commit 8de3ab18f5a154f49f02e2bee1b126a302df260d +Subproject commit 08346f917753694e14bc1caa784aa87066a2ab84 diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 2b724f21..48d98c1f 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 2b724f21a727c3fefe16adb38f06aa8730b1b8ec +Subproject commit 48d98c1f0cd80e9e569280423ae404e56047c883 diff --git a/app/src/unstable/assets/sources/dailymotion b/app/src/unstable/assets/sources/dailymotion index 850eb812..d1154300 160000 --- a/app/src/unstable/assets/sources/dailymotion +++ b/app/src/unstable/assets/sources/dailymotion @@ -1 +1 @@ -Subproject commit 850eb8122dd8348904d55ceb9c3a26b49bcb8a45 +Subproject commit d11543001150f96f3383d83fec3341d9321746b8 diff --git a/app/src/unstable/assets/sources/odysee b/app/src/unstable/assets/sources/odysee index 736c6b95..6ea9fa7e 160000 --- a/app/src/unstable/assets/sources/odysee +++ b/app/src/unstable/assets/sources/odysee @@ -1 +1 @@ -Subproject commit 736c6b953a4613145e32010ff5ee5b08be1baac6 +Subproject commit 6ea9fa7e4c20ba8c89975ac835ccebdbd1184fc4 diff --git a/app/src/unstable/assets/sources/patreon b/app/src/unstable/assets/sources/patreon index 6880b30b..b811f8bd 160000 --- a/app/src/unstable/assets/sources/patreon +++ b/app/src/unstable/assets/sources/patreon @@ -1 +1 @@ -Subproject commit 6880b30b71800f6d22ddcb692f3c1c09e745315b +Subproject commit b811f8bdfbbff73cf0d7581c9d7596911cb132b6 diff --git a/app/src/unstable/assets/sources/spotify b/app/src/unstable/assets/sources/spotify index 8c0f03f5..214ac1df 160000 --- a/app/src/unstable/assets/sources/spotify +++ b/app/src/unstable/assets/sources/spotify @@ -1 +1 @@ -Subproject commit 8c0f03f5fbc9b4e499437b85c757ec40cb7c0126 +Subproject commit 214ac1dfcc985f533d9db7d128a8315bc55fa854 diff --git a/app/src/unstable/assets/sources/twitch b/app/src/unstable/assets/sources/twitch index 8de3ab18..08346f91 160000 --- a/app/src/unstable/assets/sources/twitch +++ b/app/src/unstable/assets/sources/twitch @@ -1 +1 @@ -Subproject commit 8de3ab18f5a154f49f02e2bee1b126a302df260d +Subproject commit 08346f917753694e14bc1caa784aa87066a2ab84 diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 2b724f21..48d98c1f 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 2b724f21a727c3fefe16adb38f06aa8730b1b8ec +Subproject commit 48d98c1f0cd80e9e569280423ae404e56047c883 diff --git a/gradle.properties b/gradle.properties index 7edc7334..f6d4e25d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,9 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 +# Kotlin daemon memory settings +kotlin.daemon.jvmargs=-Xmx4096m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects