1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-08-09 10:42:50 +00:00

Added QR code.

This commit is contained in:
Koen 2023-12-03 15:12:27 +01:00
parent eed50283bb
commit b339f4f487
6 changed files with 336 additions and 37 deletions

View file

@ -84,6 +84,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0"
implementation 'com.google.android.exoplayer:exoplayer:2.18.6' implementation 'com.google.android.exoplayer:exoplayer:2.18.6'
implementation "com.squareup.okhttp3:okhttp:4.11.0" implementation "com.squareup.okhttp3:okhttp:4.11.0"
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

View file

@ -24,7 +24,6 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/Theme.Main" android:theme="@style/Theme.Main"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>

View file

@ -12,7 +12,9 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.util.Base64
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.* import android.widget.*
@ -24,7 +26,11 @@ import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ui.StyledPlayerView import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.zxing.BarcodeFormat
import com.journeyapps.barcodescanner.BarcodeEncoder
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -42,6 +48,9 @@ class MainActivity : AppCompatActivity() {
private lateinit var _videoBackground: StyledPlayerView private lateinit var _videoBackground: StyledPlayerView
private lateinit var _viewDemo: View private lateinit var _viewDemo: View
private lateinit var _player: ExoPlayer 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 _updating: Boolean = false
private var _demoClickCount = 0 private var _demoClickCount = 0
private var _lastDemoToast: Toast? = null private var _lastDemoToast: Toast? = null
@ -53,6 +62,12 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) 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) _buttonUpdate = findViewById(R.id.button_update)
_text = findViewById(R.id.text_dialog) _text = findViewById(R.id.text_dialog)
_textIPs = findViewById(R.id.text_ips) _textIPs = findViewById(R.id.text_ips)
@ -62,6 +77,8 @@ class MainActivity : AppCompatActivity() {
_layoutConnectionInfo = findViewById(R.id.layout_connection_info) _layoutConnectionInfo = findViewById(R.id.layout_connection_info)
_videoBackground = findViewById(R.id.video_background) _videoBackground = findViewById(R.id.video_background)
_viewDemo = findViewById(R.id.view_demo) _viewDemo = findViewById(R.id.view_demo)
_imageQr = findViewById(R.id.image_qr)
_textScanToConnect = findViewById(R.id.text_scan_to_connect)
startVideo() startVideo()
startAnimations() 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++ TcpListenerService.activityCount++
if (checkAndRequestPermissions()) { checkAndRequestPermissions()
Log.i(TAG, "Notification permission already granted") if (savedInstanceState == null) {
restartService()
} else {
restartService() restartService()
} }
@ -138,6 +170,11 @@ class MainActivity : AppCompatActivity() {
TcpListenerService.activityCount-- TcpListenerService.activityCount--
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
_updateAvailable?.let { outState.putBoolean("updateAvailable", it) }
}
private fun restartService() { private fun restartService() {
val i = TcpListenerService.instance val i = TcpListenerService.instance
if (i != null) { if (i != null) {
@ -294,52 +331,59 @@ class MainActivity : AppCompatActivity() {
private suspend fun checkForUpdates() { private suspend fun checkForUpdates() {
Log.i(TAG, "Checking for updates...") Log.i(TAG, "Checking for updates...")
withContext(Dispatchers.IO) { val updateAvailable = _updateAvailable
try { if (updateAvailable != null) {
val latestVersion = downloadVersionCode() setUpdateAvailable(updateAvailable)
} else {
withContext(Dispatchers.IO) {
try {
val latestVersion = downloadVersionCode()
if (latestVersion != null) { if (latestVersion != null) {
val currentVersion = BuildConfig.VERSION_CODE val currentVersion = BuildConfig.VERSION_CODE
Log.i(TAG, "Current version $currentVersion latest version $latestVersion.") Log.i(TAG, "Current version $currentVersion latest version $latestVersion.")
if (latestVersion > currentVersion) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
try { setUpdateAvailable(latestVersion > currentVersion)
(_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 { } else {
Log.w(TAG, "Failed to retrieve version from version URL.")
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
_updateSpinner.visibility = View.INVISIBLE Toast.makeText(this@MainActivity, "Failed to retrieve version", Toast.LENGTH_LONG).show()
_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()
} }
} }
} else { } catch (e: Throwable) {
Log.w(TAG, "Failed to retrieve version from version URL.") Log.w(TAG, "Failed to check for updates.", e)
withContext(Dispatchers.Main) { 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? { private fun downloadVersionCode(): Int? {
val client = OkHttpClient() val client = OkHttpClient()
val request = okhttp3.Request.Builder() val request = okhttp3.Request.Builder()

View file

@ -0,0 +1,15 @@
package com.futo.fcast.receiver
import kotlinx.serialization.Serializable
@Serializable
data class FCastNetworkConfig(
val ips: List<String>,
val services: List<FCastService>
)
@Serializable
data class FCastService(
val port: Int,
val type: Int
)

View file

@ -0,0 +1,215 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/video_background"
app:use_controller="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:resize_mode="zoom" />
<View
android:id="@+id/view_demo"
android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="FCast"
android:textColor="@color/white"
android:gravity="center"
android:fontFamily="@font/inter_bold"
android:textSize="40sp"
android:includeFontPadding="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/layout_connection_info"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_connection_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/background_manual_connect"
android:paddingStart="25dp"
android:paddingEnd="25dp"
android:paddingTop="25dp"
android:paddingBottom="25dp"
app:layout_constraintTop_toBottomOf="@id/text_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_dialog">
<TextView
android:id="@+id/text_waiting_for_connection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:gravity="center"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
android:text="Waiting for a connection"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_manual_connection_information" />
<ImageView
android:id="@+id/image_spinner"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="8dp"
app:srcCompat="@drawable/ic_loader_animated"
app:layout_constraintTop_toTopOf="@id/text_waiting_for_connection"
app:layout_constraintBottom_toBottomOf="@id/text_waiting_for_connection"
app:layout_constraintLeft_toRightOf="@id/text_waiting_for_connection" />
<TextView
android:id="@+id/text_manual_connection_information"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#919191"
android:gravity="center"
android:fontFamily="@font/inter_light"
android:textSize="12sp"
android:text="Manual connection information"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/text_waiting_for_connection"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/text_scan_to_connect"
app:layout_constraintBottom_toTopOf="parent" />
<TextView
android:id="@+id/text_ips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:gravity="center"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
tools:text="IPs\n192.168.1.3\n\nPort\n46899"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/text_manual_connection_information"
app:layout_constraintStart_toStartOf="@id/text_manual_connection_information"
app:layout_constraintEnd_toEndOf="@id/text_manual_connection_information"
app:layout_constraintBottom_toTopOf="@id/text_automatic_discovery" />
<ImageView
android:id="@+id/image_qr"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintTop_toTopOf="@id/text_ips"
app:layout_constraintStart_toStartOf="@id/text_scan_to_connect"
app:layout_constraintEnd_toEndOf="@id/text_scan_to_connect"
app:layout_constraintBottom_toBottomOf="@id/text_ips" />
<TextView
android:id="@+id/text_scan_to_connect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#919191"
android:gravity="center"
android:fontFamily="@font/inter_regular"
android:textSize="12sp"
android:text="Scan to connect"
android:layout_marginStart="50dp"
app:layout_constraintTop_toTopOf="@id/text_manual_connection_information"
app:layout_constraintBottom_toBottomOf="@id/text_manual_connection_information"
app:layout_constraintStart_toEndOf="@id/text_manual_connection_information"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/text_automatic_discovery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#919191"
android:gravity="center"
android:fontFamily="@font/inter_regular"
android:textSize="12sp"
android:text="Automatic discovery is available via mDNS"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/text_ips"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/text_dialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/there_is_an_update_available_do_you_wish_to_update"
android:textSize="14sp"
android:maxLines="2"
android:ellipsize="end"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:paddingStart="30dp"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/layout_connection_info"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/button_update" />
<LinearLayout
android:id="@+id/button_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_button_primary"
android:layout_marginEnd="2dp"
android:clickable="true"
app:layout_constraintTop_toBottomOf="@id/text_dialog"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/update"
android:textSize="14sp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingStart="28dp"
android:paddingEnd="28dp"/>
</LinearLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/text_dialog"
app:layout_constraintTop_toTopOf="@id/text_dialog"
app:layout_constraintBottom_toBottomOf="@id/text_dialog">
<ImageView
android:id="@+id/update_spinner"
android:layout_width="40dp"
android:layout_height="40dp"
app:srcCompat="@drawable/ic_update_animated" />
<TextView
android:id="@+id/text_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:layout_gravity="center"
android:textSize="10sp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -118,7 +118,32 @@
app:layout_constraintTop_toBottomOf="@id/text_ips" app:layout_constraintTop_toBottomOf="@id/text_ips"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toTopOf="@id/image_qr"/>
<ImageView
android:id="@+id/image_qr"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/text_automatic_discovery"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/text_scan_to_connect" />
<TextView
android:id="@+id/text_scan_to_connect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#919191"
android:gravity="center"
android:fontFamily="@font/inter_regular"
android:textSize="12sp"
android:text="Scan to connect"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/image_qr"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<TextView <TextView