mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Simplify Polycentric profile export with file export option
- Remove GZIP compression logic that was causing crashes - Revert to simple QR code generation with single error correction level - Add file export button when QR code is too large for scanning - Add FileProvider configuration for sharing exported files - Update string resources for new file export functionality - Keep fullscreen QR code viewer for smaller profiles This provides a more reliable solution: QR codes work for smaller profiles, and file export handles large profiles that exceed QR code limits.
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ea10d3c5562c9f449a4e89e9c3dfcf881ed79a952f3409bc005bcc62c2cf4b81
|
||||
size 65512557
|
||||
+3
-1
@@ -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')
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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, Any>(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, Any>(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 {
|
||||
|
||||
+1
-30
@@ -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";
|
||||
}
|
||||
|
||||
@@ -90,6 +90,15 @@
|
||||
app:buttonSubText="@string/copy_your_identity_to_clipboard"
|
||||
app:buttonIcon="@drawable/ic_copy"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<com.futo.platformplayer.views.buttons.BigButton
|
||||
android:id="@+id/button_export_file"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:buttonText="@string/export_to_file"
|
||||
app:buttonSubText="@string/save_profile_to_file_for_sharing"
|
||||
app:buttonIcon="@drawable/ic_download"
|
||||
android:layout_marginTop="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
|
||||
@@ -642,8 +642,11 @@
|
||||
<string name="failed_to_parse_text_file">Failed to parse text file</string>
|
||||
<string name="failed_to_parse_newpipe_subscriptions">Failed to parse NewPipe Subscriptions</string>
|
||||
<string name="failed_to_generate_qr_code">Failed to generate QR code</string>
|
||||
<string name="qr_code_too_large_use_text_below">QR code too large. Use the text below to share your profile.</string>
|
||||
<string name="qr_code_too_large_use_file_export">QR code too large. Use the file export button below to share your profile.</string>
|
||||
<string name="tap_qr_code_for_fullscreen">Tap QR code for fullscreen view</string>
|
||||
<string name="export_to_file">Export to File</string>
|
||||
<string name="save_profile_to_file_for_sharing">Save profile to file for sharing</string>
|
||||
<string name="authority">com.futo.platformplayer.fileprovider</string>
|
||||
<string name="share_text">Share Text</string>
|
||||
<string name="copied_text">Copied Text</string>
|
||||
<string name="must_be_at_least_3_characters_long">Must be at least 3 characters long.</string>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<files-path name="shares" path="shares/"/>
|
||||
<files-path name="files" path="." />
|
||||
<external-path name="external_files" path="." />
|
||||
</paths>
|
||||
Submodule app/src/stable/assets/sources/dailymotion updated: 850eb8122d...d115430011
Submodule app/src/stable/assets/sources/odysee updated: 736c6b953a...6ea9fa7e4c
Submodule app/src/stable/assets/sources/patreon updated: 6880b30b71...b811f8bdfb
Submodule app/src/stable/assets/sources/spotify updated: 8c0f03f5fb...214ac1dfcc
Submodule app/src/stable/assets/sources/twitch updated: 8de3ab18f5...08346f9177
Submodule app/src/stable/assets/sources/youtube updated: 2b724f21a7...48d98c1f0c
Submodule app/src/unstable/assets/sources/dailymotion updated: 850eb8122d...d115430011
Submodule app/src/unstable/assets/sources/odysee updated: 736c6b953a...6ea9fa7e4c
Submodule app/src/unstable/assets/sources/patreon updated: 6880b30b71...b811f8bdfb
Submodule app/src/unstable/assets/sources/spotify updated: 8c0f03f5fb...214ac1dfcc
Submodule app/src/unstable/assets/sources/twitch updated: 8de3ab18f5...08346f9177
Submodule app/src/unstable/assets/sources/youtube updated: 2b724f21a7...48d98c1f0c
+3
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user