mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-06-24 21:25:23 +00:00
Added QR code.
This commit is contained in:
parent
eed50283bb
commit
b339f4f487
6 changed files with 336 additions and 37 deletions
|
@ -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'
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:theme="@style/Theme.Main"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
)
|
215
receivers/android/app/src/main/res/layout-land/activity_main.xml
Normal file
215
receivers/android/app/src/main/res/layout-land/activity_main.xml
Normal 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>
|
|
@ -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"/>
|
||||
|
||||
<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>
|
||||
|
||||
<TextView
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue