From b339f4f487a5ae0149dde0bfdc2ed7bb12b97d47 Mon Sep 17 00:00:00 2001 From: Koen Date: Sun, 3 Dec 2023 15:12:27 +0100 Subject: [PATCH] Added QR code. --- receivers/android/app/build.gradle | 1 + .../android/app/src/main/AndroidManifest.xml | 1 - .../com/futo/fcast/receiver/MainActivity.kt | 114 +++++++--- .../java/com/futo/fcast/receiver/Models.kt | 15 ++ .../main/res/layout-land/activity_main.xml | 215 ++++++++++++++++++ .../app/src/main/res/layout/activity_main.xml | 27 ++- 6 files changed, 336 insertions(+), 37 deletions(-) create mode 100644 receivers/android/app/src/main/java/com/futo/fcast/receiver/Models.kt create mode 100644 receivers/android/app/src/main/res/layout-land/activity_main.xml diff --git a/receivers/android/app/build.gradle b/receivers/android/app/build.gradle index 3e9d910..0bd4976 100644 --- a/receivers/android/app/build.gradle +++ b/receivers/android/app/build.gradle @@ -84,6 +84,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" implementation 'com.google.android.exoplayer:exoplayer:2.18.6' implementation "com.squareup.okhttp3:okhttp:4.11.0" + implementation 'com.journeyapps:zxing-android-embedded:4.3.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/receivers/android/app/src/main/AndroidManifest.xml b/receivers/android/app/src/main/AndroidManifest.xml index b9221ab..0413338 100644 --- a/receivers/android/app/src/main/AndroidManifest.xml +++ b/receivers/android/app/src/main/AndroidManifest.xml @@ -24,7 +24,6 @@ diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt index 2f68936..3224de2 100644 --- a/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/MainActivity.kt @@ -12,7 +12,9 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings +import android.util.Base64 import android.util.Log +import android.util.TypedValue import android.view.View import android.view.WindowManager import android.widget.* @@ -24,7 +26,11 @@ import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.ui.StyledPlayerView +import com.google.zxing.BarcodeFormat +import com.journeyapps.barcodescanner.BarcodeEncoder import kotlinx.coroutines.* +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import java.io.InputStream import java.io.OutputStream @@ -42,6 +48,9 @@ class MainActivity : AppCompatActivity() { private lateinit var _videoBackground: StyledPlayerView private lateinit var _viewDemo: View private lateinit var _player: ExoPlayer + private lateinit var _imageQr: ImageView + private lateinit var _textScanToConnect: TextView + private var _updateAvailable: Boolean? = null private var _updating: Boolean = false private var _demoClickCount = 0 private var _lastDemoToast: Toast? = null @@ -53,6 +62,12 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + if (savedInstanceState != null && savedInstanceState.containsKey("updateAvailable")) { + _updateAvailable = savedInstanceState.getBoolean("updateAvailable", false) + } else { + _updateAvailable = null + } + _buttonUpdate = findViewById(R.id.button_update) _text = findViewById(R.id.text_dialog) _textIPs = findViewById(R.id.text_ips) @@ -62,6 +77,8 @@ class MainActivity : AppCompatActivity() { _layoutConnectionInfo = findViewById(R.id.layout_connection_info) _videoBackground = findViewById(R.id.video_background) _viewDemo = findViewById(R.id.view_demo) + _imageQr = findViewById(R.id.image_qr) + _textScanToConnect = findViewById(R.id.text_scan_to_connect) startVideo() startAnimations() @@ -105,13 +122,28 @@ class MainActivity : AppCompatActivity() { } } - _textIPs.text = "IPs\n" + getIPs().joinToString("\n") + "\n\nPort\n46899" + val ips = getIPs() + _textIPs.text = "IPs\n" + ips.joinToString("\n") + "\n\nPort\n46899" + + try { + val barcodeEncoder = BarcodeEncoder() + val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100.0f, resources.displayMetrics).toInt() + val json = Json.encodeToString(FCastNetworkConfig(ips, listOf( + FCastService(46899, 0) + ))) + val base64 = Base64.encode(json.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP) + val url = "fcast://r/${base64}" + val bitmap = barcodeEncoder.encodeBitmap(url, BarcodeFormat.QR_CODE, px, px) + _imageQr.setImageBitmap(bitmap) + } catch (e: java.lang.Exception) { + _textScanToConnect.visibility = View.GONE + _imageQr.visibility = View.GONE + } + TcpListenerService.activityCount++ - if (checkAndRequestPermissions()) { - Log.i(TAG, "Notification permission already granted") - restartService() - } else { + checkAndRequestPermissions() + if (savedInstanceState == null) { restartService() } @@ -138,6 +170,11 @@ class MainActivity : AppCompatActivity() { TcpListenerService.activityCount-- } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + _updateAvailable?.let { outState.putBoolean("updateAvailable", it) } + } + private fun restartService() { val i = TcpListenerService.instance if (i != null) { @@ -294,52 +331,59 @@ class MainActivity : AppCompatActivity() { private suspend fun checkForUpdates() { Log.i(TAG, "Checking for updates...") - withContext(Dispatchers.IO) { - try { - val latestVersion = downloadVersionCode() + val updateAvailable = _updateAvailable + if (updateAvailable != null) { + setUpdateAvailable(updateAvailable) + } else { + withContext(Dispatchers.IO) { + try { + val latestVersion = downloadVersionCode() - if (latestVersion != null) { - val currentVersion = BuildConfig.VERSION_CODE - Log.i(TAG, "Current version $currentVersion latest version $latestVersion.") + if (latestVersion != null) { + val currentVersion = BuildConfig.VERSION_CODE + Log.i(TAG, "Current version $currentVersion latest version $latestVersion.") - if (latestVersion > currentVersion) { withContext(Dispatchers.Main) { - try { - (_updateSpinner.drawable as Animatable?)?.stop() - _updateSpinner.visibility = View.INVISIBLE - setText(resources.getText(R.string.there_is_an_update_available_do_you_wish_to_update)) - _buttonUpdate.visibility = View.VISIBLE - } catch (e: Throwable) { - Toast.makeText(this@MainActivity, "Failed to show update dialog", Toast.LENGTH_LONG).show() - Log.w(TAG, "Error occurred in update dialog.") - } + setUpdateAvailable(latestVersion > currentVersion) } } else { + Log.w(TAG, "Failed to retrieve version from version URL.") + withContext(Dispatchers.Main) { - _updateSpinner.visibility = View.INVISIBLE - _buttonUpdate.visibility = View.INVISIBLE - //setText(getString(R.string.no_updates_available)) - setText(null) - //Toast.makeText(this@MainActivity, "Already on latest version", Toast.LENGTH_LONG).show() + Toast.makeText(this@MainActivity, "Failed to retrieve version", Toast.LENGTH_LONG).show() } } - } else { - Log.w(TAG, "Failed to retrieve version from version URL.") + } catch (e: Throwable) { + Log.w(TAG, "Failed to check for updates.", e) withContext(Dispatchers.Main) { - Toast.makeText(this@MainActivity, "Failed to retrieve version", Toast.LENGTH_LONG).show() + Toast.makeText(this@MainActivity, "Failed to check for updates", Toast.LENGTH_LONG).show() } } - } catch (e: Throwable) { - Log.w(TAG, "Failed to check for updates.", e) - - withContext(Dispatchers.Main) { - Toast.makeText(this@MainActivity, "Failed to check for updates", Toast.LENGTH_LONG).show() - } } } } + private fun setUpdateAvailable(updateAvailable: Boolean) { + if (updateAvailable) { + try { + (_updateSpinner.drawable as Animatable?)?.stop() + _updateSpinner.visibility = View.INVISIBLE + setText(resources.getText(R.string.there_is_an_update_available_do_you_wish_to_update)) + _buttonUpdate.visibility = View.VISIBLE + } catch (e: Throwable) { + Toast.makeText(this@MainActivity, "Failed to show update dialog", Toast.LENGTH_LONG).show() + Log.w(TAG, "Error occurred in update dialog.") + } + } else { + _updateSpinner.visibility = View.INVISIBLE + _buttonUpdate.visibility = View.INVISIBLE + setText(null) + } + + _updateAvailable = updateAvailable + } + private fun downloadVersionCode(): Int? { val client = OkHttpClient() val request = okhttp3.Request.Builder() diff --git a/receivers/android/app/src/main/java/com/futo/fcast/receiver/Models.kt b/receivers/android/app/src/main/java/com/futo/fcast/receiver/Models.kt new file mode 100644 index 0000000..cc2be4f --- /dev/null +++ b/receivers/android/app/src/main/java/com/futo/fcast/receiver/Models.kt @@ -0,0 +1,15 @@ +package com.futo.fcast.receiver + +import kotlinx.serialization.Serializable + +@Serializable +data class FCastNetworkConfig( + val ips: List, + val services: List +) + +@Serializable +data class FCastService( + val port: Int, + val type: Int +) \ No newline at end of file diff --git a/receivers/android/app/src/main/res/layout-land/activity_main.xml b/receivers/android/app/src/main/res/layout-land/activity_main.xml new file mode 100644 index 0000000..5789868 --- /dev/null +++ b/receivers/android/app/src/main/res/layout-land/activity_main.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/receivers/android/app/src/main/res/layout/activity_main.xml b/receivers/android/app/src/main/res/layout/activity_main.xml index cd35a2d..8ade1f7 100644 --- a/receivers/android/app/src/main/res/layout/activity_main.xml +++ b/receivers/android/app/src/main/res/layout/activity_main.xml @@ -118,7 +118,32 @@ app:layout_constraintTop_toBottomOf="@id/text_ips" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toBottomOf="parent" /> + app:layout_constraintBottom_toTopOf="@id/image_qr"/> + + + +