diff --git a/.github/workflows/build_debug_apk.yml b/.github/workflows/build_debug_apk.yml
new file mode 100644
index 0000000..25833b5
--- /dev/null
+++ b/.github/workflows/build_debug_apk.yml
@@ -0,0 +1,38 @@
+name: "build debug apk"
+
+on:
+ push:
+ branches: [ "develop" ]
+ tags: [ "d_*" ]
+
+jobs:
+ buildDebug:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: prepare_vars
+ id: prepare
+ run: |
+ echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
+ echo "::set-output name=tag_short::${GITHUB_REF##*/}"
+
+ - name: setup_java
+ uses: actions/setup-java@v2
+ with:
+ distribution: zulu
+ java-version: 11
+
+ - name: assemble
+ run: ./gradlew assembleDebug
+
+ - name: upload
+ uses: wei/curl@v1.1.1
+ env:
+ APP_NAME: f1dashboard
+ APP_VERSION: ${{ steps.prepare.outputs.tag_short }}-${{ steps.prepare.outputs.sha_short }}
+ URL: https://nexus.n1ks.it
+ USERNAME: ${{ secrets.NEXUS_USERNAME }}
+ PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
+ with:
+ args: -u $USERNAME:$PASSWORD "$URL//service/rest/v1/components?repository=debugpub" -F "raw.directory=$APP_NAME" -F "raw.asset1=@app/build/outputs/apk/debug/app-debug.apk" -F "raw.asset1.filename=$APP_NAME-$APP_VERSION.apk"
diff --git a/.github/workflows/build_release_apk.yml b/.github/workflows/build_release_apk.yml
new file mode 100644
index 0000000..789d6e0
--- /dev/null
+++ b/.github/workflows/build_release_apk.yml
@@ -0,0 +1,68 @@
+name: "build release apk"
+
+on:
+ push:
+ tags: [ "v*" ]
+
+jobs:
+ buildRelease:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: prepare_vars
+ id: prepare
+ run: |
+ echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
+ echo "::set-output name=tag_short::${GITHUB_REF##*/}"
+
+ - name: setup_java
+ uses: actions/setup-java@v2
+ with:
+ distribution: zulu
+ java-version: 11
+
+ - name: assemble
+ run: ./gradlew assembleRelease
+
+ - name: sign
+ id: sign
+ uses: r0adkll/sign-android-release@v1
+ env:
+ BUILD_TOOLS_VERSION: 30.0.3
+ with:
+ releaseDirectory: app/build/outputs/apk/release
+ signingKeyBase64: ${{ secrets.SIGNING_KEY }}
+ alias: ${{ secrets.SIGNING_KEY_ALIAS }}
+ keyStorePassword: ${{ secrets.SIGING_PASSWORD }}
+ keyPassword: ${{ secrets.SIGING_PASSWORD }}
+
+ - name: apk_rename
+ id: rename
+ env:
+ APP_NAME: f1dashboard
+ APP_VERSION: ${{ steps.prepare.outputs.tag_short }}
+ run: |
+ mv ${{steps.sign.outputs.signedReleaseFile}} $APP_NAME-$APP_VERSION.apk
+ echo "::set-output name=file_name::$APP_NAME-$APP_VERSION.apk"
+
+ - name: upload_nexus
+ uses: wei/curl@v1.1.1
+ env:
+ APP_NAME: f1dashboard
+ APP_VERSION: ${{ steps.prepare.outputs.tag_short }}
+ URL: https://nexus.n1ks.it
+ USERNAME: ${{ secrets.NEXUS_USERNAME }}
+ PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
+ with:
+ args: -u $USERNAME:$PASSWORD "$URL//service/rest/v1/components?repository=public" -F "raw.directory=$APP_NAME" -F "raw.asset1=@${{steps.rename.outputs.file_name}}" -F "raw.asset1.filename=$APP_NAME-$APP_VERSION.apk"
+
+
+ - name: release
+ uses: marvinpinto/action-automatic-releases@latest
+ with:
+ repo_token: "${{ secrets.GITHUB_TOKEN }}"
+ automatic_release_tag: "latest"
+ prerelease: false
+ title: ${{ steps.prepare.outputs.tag_short }}
+ files: ${{steps.rename.outputs.file_name}}
diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml
new file mode 100644
index 0000000..78fd53d
--- /dev/null
+++ b/.github/workflows/build_test.yml
@@ -0,0 +1,23 @@
+name: "test build"
+
+on:
+ push:
+ branches: [ "master", "develop", "feature/*", "bugfix/*" ]
+ pull_request:
+ branches: [ "master", "develop" ]
+ workflow_dispatch:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: setup_java
+ uses: actions/setup-java@v2
+ with:
+ distribution: zulu
+ java-version: 11
+
+ - name: run_tests
+ run: ./gradlew test
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index a5f05cd..e34606c 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -21,5 +21,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index aac84cd..e35bae3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
+ id 'org.jetbrains.kotlin.kapt'
}
android {
@@ -29,6 +30,10 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
+ freeCompilerArgs = ['-Xjvm-default=enable']
+ }
+ lintOptions {
+ abortOnError false
}
}
@@ -42,7 +47,15 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
- testImplementation 'junit:junit:4.+'
+
+ implementation "ch.acra:acra-core:$acra_version"
+ implementation("ch.acra:acra-toast:$acra_version")
+ implementation("ch.acra:acra-dialog:$acra_version")
+
+ compileOnly("com.google.auto.service:auto-service-annotations:1.0")
+ kapt("com.google.auto.service:auto-service:1.0")
+
+ testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 36b4637..91d2805 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,23 +2,26 @@
-
-
-
+
+
+
+
-
@@ -26,6 +29,14 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/DashboardApplication.kt b/app/src/main/java/ru/n1ks/f1dashboard/DashboardApplication.kt
new file mode 100644
index 0000000..ff0a2bd
--- /dev/null
+++ b/app/src/main/java/ru/n1ks/f1dashboard/DashboardApplication.kt
@@ -0,0 +1,26 @@
+package ru.n1ks.f1dashboard
+
+import android.app.Application
+import android.content.Context
+import org.acra.config.dialog
+import org.acra.data.StringFormat
+import org.acra.ktx.initAcra
+import ru.n1ks.f1dashboard.reporting.CrashReportActivity
+
+class DashboardApplication : Application() {
+
+ override fun attachBaseContext(base: Context?) {
+ super.attachBaseContext(base)
+
+ initAcra {
+ //core configuration:
+ buildConfigClass = BuildConfig::class.java
+ reportFormat = StringFormat.JSON
+
+ dialog {
+ //allows other customization
+ reportDialogClass = CrashReportActivity::class.java
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/ListenerService.kt b/app/src/main/java/ru/n1ks/f1dashboard/ListenerService.kt
index b77233b..d5d2dac 100644
--- a/app/src/main/java/ru/n1ks/f1dashboard/ListenerService.kt
+++ b/app/src/main/java/ru/n1ks/f1dashboard/ListenerService.kt
@@ -18,7 +18,6 @@ import java.util.*
import java.util.concurrent.atomic.AtomicLong
-@ExperimentalUnsignedTypes
class ListenerService : Service() {
companion object {
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/MainActivity.kt b/app/src/main/java/ru/n1ks/f1dashboard/MainActivity.kt
index 91b3213..e4fd112 100644
--- a/app/src/main/java/ru/n1ks/f1dashboard/MainActivity.kt
+++ b/app/src/main/java/ru/n1ks/f1dashboard/MainActivity.kt
@@ -20,7 +20,6 @@ import ru.n1ks.f1dashboard.livedata.LiveData
import ru.n1ks.f1dashboard.livedata.LiveDataFields
import java.util.concurrent.TimeUnit
-@ExperimentalUnsignedTypes
class MainActivity : AppCompatActivity() {
companion object {
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/helpers.kt b/app/src/main/java/ru/n1ks/f1dashboard/helpers.kt
index 8fda73d..cb7692c 100644
--- a/app/src/main/java/ru/n1ks/f1dashboard/helpers.kt
+++ b/app/src/main/java/ru/n1ks/f1dashboard/helpers.kt
@@ -1,10 +1,11 @@
package ru.n1ks.f1dashboard
-@ExperimentalUnsignedTypes
-internal val UByteOne = 1.toUByte()
+internal object Bytes {
-@ExperimentalUnsignedTypes
-internal fun UByte.plusOne() = this.plus(UByteOne).toUByte()
+ const val One = 1.toByte()
+ const val Two = 2.toByte()
+}
-@ExperimentalUnsignedTypes
-internal fun UByte.minusOne() = this.minus(UByteOne).toUByte()
\ No newline at end of file
+internal infix fun Byte.plusByte(increment: Byte): Byte = this.plus(increment).toByte()
+
+internal infix fun Byte.minusByte(decrement: Byte): Byte = this.minus(decrement).toByte()
\ No newline at end of file
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/livedata/live_data.kt b/app/src/main/java/ru/n1ks/f1dashboard/livedata/live_data.kt
index 9feb622..586b600 100644
--- a/app/src/main/java/ru/n1ks/f1dashboard/livedata/live_data.kt
+++ b/app/src/main/java/ru/n1ks/f1dashboard/livedata/live_data.kt
@@ -8,48 +8,115 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.rxkotlin.toSingle
-import ru.n1ks.f1dashboard.R
-import ru.n1ks.f1dashboard.minusOne
+import ru.n1ks.f1dashboard.*
import ru.n1ks.f1dashboard.model.*
-import ru.n1ks.f1dashboard.plusOne
import java.text.DecimalFormat
import java.util.concurrent.TimeUnit
+import kotlin.math.absoluteValue
+
+
+interface ViewProvider {
+
+ fun findViewById(id: Int): T
+ fun getDrawable(id: Int): Drawable
+ fun getColor(id: Int): Int
+}
enum class DrsCommonState {
Available, Unavailable, Upcoming, Fault
}
-@ExperimentalUnsignedTypes
data class CompetitorDriver(
val code: Int,
val driver: ParticipantData.Driver
)
-@ExperimentalUnsignedTypes
data class Competitor(
val id: Int,
val position: Int,
val lastLapTime: Float,
val bestLapTime: Float,
val lap: Int,
+ val visualTyreType: TyreCompound,
+ val actualTyreType: TyreCompound,
+ val tyreAge: Int,
val driver: CompetitorDriver?
-// val tyreType: TyreCompound
-)
+) {
+
+ val positionString: String
+ get() = position.toString().padEnd(2, ' ')
+
+ val isTyresNew: Boolean
+ get() = tyreAge < 3
-data class TyreState(
+ val tyreTypeValue: String
+ get() {
+ return if (visualTyreType == actualTyreType) {
+ visualTyreType.char.toString()
+ } else {
+ when {
+ visualTyreType == TyreCompound.X -> actualTyreType.char.toString()
+ actualTyreType == TyreCompound.X -> visualTyreType.char.toString()
+ else -> actualTyreType.char.toString() + visualTyreType.char.toString()
+ }
+ }
+ }
+
+ val tyreTyreColor: Int
+ get() = if (visualTyreType != TyreCompound.X) visualTyreType.color else actualTyreType.color
+
+ fun inBound(size: Int): Boolean = id in 0 until size
+}
+
+data class TyreStateField(
val wear: Int,
val innerTemperature: Int,
val outerTemperature: Int
-)
+) {
-interface ViewProvider {
+ val wearColor: Int
+ get() {
+ return when {
+ wear < 5 -> R.color.wear0
+ wear < 15 -> R.color.wear10
+ wear < 23 -> R.color.wear20
+ wear < 32 -> R.color.wear30
+ wear < 40 -> R.color.wear40
+ wear < 50 -> R.color.wear50
+ wear < 60 -> R.color.wear60
+ wear < 70 -> R.color.wear70
+ wear < 80 -> R.color.wear80
+ wear < 90 -> R.color.wear90
+ wear <= 100 -> R.color.wear100
+ else -> R.color.inop
+ }
+ }
- fun findViewById(id: Int): View
- fun getDrawable(id: Int): Drawable
- fun getColor(id: Int): Int
+ val wearValue: String
+ get() {
+ return if (wear < 0 || wear > 99) {
+ "XX"
+ } else {
+ wear.toString()
+ }
+ }
+
+ val innerTemperatureColor: Int
+ get() = defineTempColor(innerTemperature)
+
+ val outerTemperatureColor: Int
+ get() = defineTempColor(outerTemperature)
+
+ private fun defineTempColor(temp: Int): Int {
+ return when {
+ temp < 82 -> R.color.lowTemp
+ temp < 103 -> R.color.normalTemp
+ temp < 110 -> R.color.warmTemp
+ else -> R.color.highTemp
+ }
+ }
}
-@ExperimentalUnsignedTypes
class LiveDataField(
val name: String,
private var data: T,
@@ -77,45 +144,26 @@ data class DrsField(
val isOpened: Boolean
)
-@ExperimentalUnsignedTypes
data class RivalsField(
+ val ahead2: Competitor?,
val ahead: Competitor?,
val player: Competitor?,
- val behind: Competitor?
+ val behind: Competitor?,
+ val behind2: Competitor?
)
-data class TypesField(
- val tyreFL: TyreState,
- val tyreFR: TyreState,
- val tyreRL: TyreState,
- val tyreRR: TyreState
+data class BestLapField(
+ val competitorId: Int,
+ val driver: ParticipantData.Driver?,
+ val bestLapTime: Float
)
-private fun defineWearColor(wear: Int): Int {
- return when {
- wear < 5 -> R.color.wear0
- wear < 15 -> R.color.wear10
- wear < 23 -> R.color.wear20
- wear < 32 -> R.color.wear30
- wear < 40 -> R.color.wear40
- wear < 50 -> R.color.wear50
- wear < 60 -> R.color.wear60
- wear < 70 -> R.color.wear70
- wear < 80 -> R.color.wear80
- wear < 90 -> R.color.wear90
- wear <= 100 -> R.color.wear100
- else -> R.color.inop
- }
-}
-
-private fun defineTempColor(temp: Int): Int {
- return when {
- temp < 82 -> R.color.lowTemp
- temp < 103 -> R.color.normalTemp
- temp < 110 -> R.color.warmTemp
- else -> R.color.highTemp
- }
-}
+data class TypesField(
+ val tyreFL: TyreStateField,
+ val tyreFR: TyreStateField,
+ val tyreRL: TyreStateField,
+ val tyreRR: TyreStateField
+)
private val secondsAndMsFormat: DecimalFormat = DecimalFormat("00.000")
private val secondsFormat: DecimalFormat = DecimalFormat("00")
@@ -123,10 +171,13 @@ private val fuelFormat: DecimalFormat = DecimalFormat("+#,#0.00;-#")
private val timeFormatter: (Float) -> String = {
val n1 = it.toInt() / 60
val n2 = it % 60
- "${n1}:${secondsAndMsFormat.format(n2)}"
+ if (n1 == 0 && n2.absoluteValue < 0.001f) {
+ "X:XX.XXX"
+ } else {
+ "${n1}:${secondsAndMsFormat.format(n2)}"
+ }
}
-@ExperimentalUnsignedTypes
class LiveData(
private val activity: Activity,
private val fields: Collection>
@@ -135,16 +186,17 @@ class LiveData(
private val views = HashMap()
private val viewProvider = object : ViewProvider {
- override fun findViewById(id: Int): View =
+ @Suppress("UNCHECKED_CAST")
+ override fun findViewById(id: Int): T =
views[id].let {
if (it == null) {
- val view = activity.findViewById(id)
+ val view = activity.findViewById(id)
views[id] = view
view
} else {
it
}
- }
+ } as T
override fun getDrawable(id: Int): Drawable =
ContextCompat.getDrawable(activity, id)!!
@@ -161,7 +213,6 @@ class LiveData(
}
@SuppressLint("SetTextI18n")
-@ExperimentalUnsignedTypes
val LiveDataFields = listOf>(
LiveDataField(
"lapRemaining",
@@ -181,7 +232,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val lapField = findViewById(R.id.lapValue) as TextView
+ val lapField = findViewById(R.id.lapValue)
lapField.text = if (it.lapsCount - it.currentLap >= 0) {
"+${it.lapsCount - it.currentLap}"
} else {
@@ -199,7 +250,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val fuelField = findViewById(R.id.fuelValue) as TextView
+ val fuelField = findViewById(R.id.fuelValue)
fuelField.text = fuelFormat.format(it)
}
),
@@ -213,7 +264,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val fuelField = findViewById(R.id.fuelValue) as TextView
+ val fuelField = findViewById(R.id.fuelValue)
when (it) {
1 -> {
fuelField.background = getDrawable(R.color.leanMode)
@@ -240,7 +291,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val ersField = findViewById(R.id.ersValue) as TextView
+ val ersField = findViewById(R.id.ersValue)
ersField.text = it.toString()
when (it) {
0 -> {
@@ -268,7 +319,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val bbField = findViewById(R.id.bbValue) as TextView
+ val bbField = findViewById(R.id.bbValue)
bbField.text = it.toString()
bbField.background = getDrawable(R.color.warn)
val tag = System.nanoTime()
@@ -291,7 +342,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val diffField = findViewById(R.id.diffValue) as TextView
+ val diffField = findViewById(R.id.diffValue)
diffField.text = it.toString()
diffField.background = getDrawable(R.color.warn)
val tag = System.nanoTime()
@@ -314,7 +365,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val recommendedGearField = findViewById(R.id.recommendedGearValue) as TextView
+ val recommendedGearField = findViewById(R.id.recommendedGearValue)
if (it > 0) {
recommendedGearField.text = it.toString()
} else {
@@ -355,7 +406,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
onUpdateFunc = {
- val drsField = findViewById(R.id.drsValue) as TextView
+ val drsField = findViewById(R.id.drsValue)
when (it.state) {
DrsCommonState.Unavailable -> {
drsField.text = "X"
@@ -386,23 +437,34 @@ val LiveDataFields = listOf>(
),
LiveDataField(
"rivals",
- RivalsField(ahead = null, player = null, behind = null),
+ RivalsField(ahead2 = null, ahead = null, player = null, behind = null, behind2 = null),
{ data, packet ->
packet.asType { it ->
val playerData = it.data.items[it.header.playerCarIndex]
+ val playerIndex =
+ it.data.items.indexOfFirst { item -> item.carPosition == playerData.carPosition }
val rivalAheadIndex =
- it.data.items.indexOfFirst { item -> item.carPosition == playerData.carPosition.minusOne() }
+ it.data.items.indexOfFirst { item -> item.carPosition == playerData.carPosition minusByte Bytes.One }
val rivalAheadData = it.data.items.getOrNull(rivalAheadIndex)
+ val rivalAhead2Index =
+ it.data.items.indexOfFirst { item -> item.carPosition == playerData.carPosition minusByte Bytes.Two }
+ val rivalAhead2Data = it.data.items.getOrNull(rivalAhead2Index)
val rivalBehindIndex =
- it.data.items.indexOfFirst { item -> item.carPosition == playerData.carPosition.plusOne() }
+ it.data.items.indexOfFirst { item -> item.carPosition == playerData.carPosition plusByte Bytes.One }
val rivalBehindData = it.data.items.getOrNull(rivalBehindIndex)
+ val rivalBehind2Index =
+ it.data.items.indexOfFirst { item -> item.carPosition == playerData.carPosition plusByte Bytes.Two }
+ val rivalBehind2Data = it.data.items.getOrNull(rivalBehind2Index)
val player = Competitor(
- id = -1,
+ id = playerIndex,
position = playerData.carPosition.toInt(),
lastLapTime = playerData.lastLapTime,
bestLapTime = playerData.bestLapTime,
lap = playerData.currentLapNum.toInt(),
+ visualTyreType = data.player?.visualTyreType ?: TyreCompound.X,
+ actualTyreType = data.player?.actualTyreType ?: TyreCompound.X,
+ tyreAge = data.player?.tyreAge ?: Int.MAX_VALUE,
driver = null
)
val ahead = rivalAheadData?.let {
@@ -412,9 +474,25 @@ val LiveDataFields = listOf>(
lastLapTime = it.lastLapTime,
bestLapTime = it.bestLapTime,
lap = it.currentLapNum.toInt(),
+ visualTyreType = if (data.ahead?.id == rivalAheadIndex) data.ahead.visualTyreType else TyreCompound.X,
+ actualTyreType = if (data.ahead?.id == rivalAheadIndex) data.ahead.actualTyreType else TyreCompound.X,
+ tyreAge = data.ahead?.tyreAge ?: Int.MAX_VALUE,
driver = if (data.ahead?.id == rivalAheadIndex) data.ahead.driver else null
)
}
+ val ahead2 = rivalAhead2Data?.let {
+ Competitor(
+ id = rivalAhead2Index,
+ position = it.carPosition.toInt(),
+ lastLapTime = it.lastLapTime,
+ bestLapTime = it.bestLapTime,
+ lap = it.currentLapNum.toInt(),
+ visualTyreType = if (data.ahead2?.id == rivalAhead2Index) data.ahead2.visualTyreType else TyreCompound.X,
+ actualTyreType = if (data.ahead2?.id == rivalAhead2Index) data.ahead2.actualTyreType else TyreCompound.X,
+ tyreAge = data.ahead2?.tyreAge ?: Int.MAX_VALUE,
+ driver = if (data.ahead2?.id == rivalAhead2Index) data.ahead2.driver else null
+ )
+ }
val behind = rivalBehindData?.let {
Competitor(
id = rivalBehindIndex,
@@ -422,15 +500,31 @@ val LiveDataFields = listOf>(
lastLapTime = it.lastLapTime,
bestLapTime = it.bestLapTime,
lap = it.currentLapNum.toInt(),
+ visualTyreType = if (data.behind?.id == rivalBehindIndex) data.behind.visualTyreType else TyreCompound.X,
+ actualTyreType = if (data.behind?.id == rivalBehindIndex) data.behind.actualTyreType else TyreCompound.X,
+ tyreAge = data.behind?.tyreAge ?: Int.MAX_VALUE,
driver = if (data.behind?.id == rivalBehindIndex) data.behind.driver else null
)
}
- return@LiveDataField RivalsField(ahead, player, behind)
+ val behind2 = rivalBehind2Data?.let {
+ Competitor(
+ id = rivalBehind2Index,
+ position = it.carPosition.toInt(),
+ lastLapTime = it.lastLapTime,
+ bestLapTime = it.bestLapTime,
+ lap = it.currentLapNum.toInt(),
+ visualTyreType = if (data.behind2?.id == rivalBehind2Index) data.behind2.visualTyreType else TyreCompound.X,
+ actualTyreType = if (data.behind2?.id == rivalBehind2Index) data.behind2.actualTyreType else TyreCompound.X,
+ tyreAge = data.behind2?.tyreAge ?: Int.MAX_VALUE,
+ driver = if (data.behind2?.id == rivalBehind2Index) data.behind2.driver else null
+ )
+ }
+ return@LiveDataField RivalsField(ahead2, ahead, player, behind, behind2)
}
packet.asType {
var newData = data
- if (data.ahead != null && data.ahead.id >= 0 && data.ahead.id < it.data.items.size) {
- newData = data.copy(
+ if (data.ahead?.inBound(it.data.items.size) == true) {
+ newData = newData.copy(
ahead = data.ahead.copy(
driver = CompetitorDriver(
it.data.items[data.ahead.id].raceNumber.toInt(),
@@ -439,7 +533,17 @@ val LiveDataFields = listOf>(
)
)
}
- if (data.behind != null && data.behind.id >= 0 && data.behind.id < it.data.items.size) {
+ if (data.ahead2?.inBound(it.data.items.size) == true) {
+ newData = newData.copy(
+ ahead2 = data.ahead2.copy(
+ driver = CompetitorDriver(
+ it.data.items[data.ahead2.id].raceNumber.toInt(),
+ it.data.items[data.ahead2.id].driver
+ )
+ )
+ )
+ }
+ if (data.behind?.inBound(it.data.items.size) == true) {
newData = newData.copy(
behind = data.behind.copy(
driver = CompetitorDriver(
@@ -449,100 +553,305 @@ val LiveDataFields = listOf>(
)
)
}
+ if (data.behind2?.inBound(it.data.items.size) == true) {
+ newData = newData.copy(
+ behind2 = data.behind2.copy(
+ driver = CompetitorDriver(
+ it.data.items[data.behind2.id].raceNumber.toInt(),
+ it.data.items[data.behind2.id].driver
+ )
+ )
+ )
+ }
return@LiveDataField newData
}
- return@LiveDataField data
- },
- { context ->
- val aheadDeltaField = findViewById(R.id.aheadDeltaValue) as TextView
- val aheadTimeField = findViewById(R.id.aheadTimeValue) as TextView
-
- if (context.ahead != null && context.ahead.position > 0) {
- aheadDeltaField.text =
- context.ahead.position.toString() + context.ahead.driver.let { if (it != null) " ${it.driver.name}" else "" }
- aheadTimeField.text = timeFormatter(context.ahead.lastLapTime)
-
- when {
- context.ahead.lastLapTime < context.player?.lastLapTime ?: Float.MIN_VALUE -> aheadTimeField.setTextColor(
- getColor(R.color.timeBetter)
+ packet.asType {
+ var newData = data
+ if (data.player?.inBound(it.data.items.size) == true) {
+ newData = data.copy(
+ player = data.player.copy(
+ visualTyreType = it.data.items[data.player.id].visualTyreCompound,
+ actualTyreType = it.data.items[data.player.id].actualTyreCompound,
+ tyreAge = it.data.items[data.player.id].tyresAgeLaps.toInt()
+ )
)
- context.ahead.lastLapTime > context.player?.lastLapTime ?: Float.MAX_VALUE -> aheadTimeField.setTextColor(
- getColor(R.color.timeWorse)
+ }
+ if (data.ahead?.inBound(it.data.items.size) == true) {
+ newData = newData.copy(
+ ahead = data.ahead.copy(
+ visualTyreType = it.data.items[data.ahead.id].visualTyreCompound,
+ actualTyreType = it.data.items[data.ahead.id].actualTyreCompound,
+ tyreAge = it.data.items[data.ahead.id].tyresAgeLaps.toInt()
+ )
)
- else -> aheadTimeField.setTextColor(getColor(R.color.white))
}
- } else {
- aheadDeltaField.text = "X"
- aheadTimeField.text = "X"
- aheadTimeField.setTextColor(getColor(R.color.white))
+ if (data.ahead2?.inBound(it.data.items.size) == true) {
+ newData = newData.copy(
+ ahead2 = data.ahead2.copy(
+ visualTyreType = it.data.items[data.ahead2.id].visualTyreCompound,
+ actualTyreType = it.data.items[data.ahead2.id].actualTyreCompound,
+ tyreAge = it.data.items[data.ahead2.id].tyresAgeLaps.toInt()
+ )
+ )
+ }
+ if (data.behind?.inBound(it.data.items.size) == true) {
+ newData = newData.copy(
+ behind = data.behind.copy(
+ visualTyreType = it.data.items[data.behind.id].visualTyreCompound,
+ actualTyreType = it.data.items[data.behind.id].actualTyreCompound,
+ tyreAge = it.data.items[data.behind.id].tyresAgeLaps.toInt()
+ )
+ )
+ }
+ if (data.behind2?.inBound(it.data.items.size) == true) {
+ newData = newData.copy(
+ behind2 = data.behind2.copy(
+ visualTyreType = it.data.items[data.behind2.id].visualTyreCompound,
+ actualTyreType = it.data.items[data.behind2.id].actualTyreCompound,
+ tyreAge = it.data.items[data.behind2.id].tyresAgeLaps.toInt()
+ )
+ )
+ }
+ return@LiveDataField newData
}
+ return@LiveDataField data
+ },
+ { context ->
+ run {
+ val aheadDriverField = findViewById(R.id.aheadDriverValue)
+ val aheadTimeField = findViewById(R.id.aheadTimeValue)
+ val aheadTyreField = findViewById(R.id.aheadTyreValue)
- val playerBestTimeField = findViewById(R.id.myBestValue) as TextView
- val playerLastTimeField = findViewById(R.id.myTimeValue) as TextView
+ if (context.ahead != null && context.ahead.position > 0) {
+ aheadDriverField.text =
+ context.ahead.positionString + context.ahead.driver.let { if (it != null) " ${it.driver.name}" else "" }
+ aheadTimeField.text =
+ timeFormatter(context.ahead.lastLapTime)
- if (context.player != null) {
- playerBestTimeField.text = timeFormatter(context.player.bestLapTime)
- playerLastTimeField.text = timeFormatter(context.player.lastLapTime)
+ when {
+ context.ahead.lastLapTime < context.player?.lastLapTime ?: Float.MIN_VALUE -> aheadTimeField.setTextColor(
+ getColor(R.color.timeBetter)
+ )
+ context.ahead.lastLapTime > context.player?.lastLapTime ?: Float.MAX_VALUE -> aheadTimeField.setTextColor(
+ getColor(R.color.timeWorse)
+ )
+ else -> aheadTimeField.setTextColor(getColor(R.color.white))
+ }
- if (context.player.lap < context.ahead?.lap ?: context.player.lap) {
- playerLastTimeField.setTextColor(getColor(R.color.timeIrrelevant))
+ aheadTyreField.text = context.ahead.tyreTypeValue
+ aheadTyreField.setTextColor(getColor(context.ahead.tyreTyreColor))
+ aheadTyreField.background =
+ if (context.ahead.isTyresNew) getDrawable(R.color.tyreNew) else null
} else {
- playerLastTimeField.setTextColor(getColor(R.color.white))
+ aheadDriverField.text = "XX"
+ aheadTimeField.text = "X:XX.XXX"
+ aheadTimeField.setTextColor(getColor(R.color.white))
+ aheadTyreField.text = "X"
+ aheadTyreField.setTextColor(getColor(R.color.white))
+ aheadTyreField.background = null
}
- } else {
- playerBestTimeField.text = "X"
- playerLastTimeField.text = "X"
}
- val behindDeltaField = findViewById(R.id.behindDeltaValue) as TextView
- val behindTimeField = findViewById(R.id.behindTimeValue) as TextView
+ run {
+ val ahead2DriverField = findViewById(R.id.ahead2DriverValue)
+ val ahead2TimeField = findViewById(R.id.ahead2TimeValue)
+ val ahead2TyreField = findViewById(R.id.ahead2TyreValue)
- if (context.behind != null) {
- behindDeltaField.text =
- context.behind.position.toString() + context.behind.driver.let { if (it != null) " ${it.driver.name}" else "" }
- behindTimeField.text = timeFormatter(context.behind.lastLapTime)
+ if (context.ahead2 != null && context.ahead2.position > 0) {
+ ahead2DriverField.text =
+ context.ahead2.positionString + context.ahead2.driver.let { if (it != null) " ${it.driver.name}" else "" }
+ ahead2TimeField.text =
+ timeFormatter(context.ahead2.lastLapTime)
- if (context.behind.lap < context.player?.lap ?: context.behind.lap) {
- behindTimeField.setTextColor(getColor(R.color.timeIrrelevant))
- } else {
when {
- context.behind.lastLapTime < context.player?.lastLapTime ?: Float.MIN_VALUE -> behindTimeField.setTextColor(
+ context.ahead2.lastLapTime < context.player?.lastLapTime ?: Float.MIN_VALUE -> ahead2TimeField.setTextColor(
getColor(R.color.timeBetter)
)
- context.behind.lastLapTime > context.player?.lastLapTime ?: Float.MAX_VALUE -> behindTimeField.setTextColor(
+ context.ahead2.lastLapTime > context.player?.lastLapTime ?: Float.MAX_VALUE -> ahead2TimeField.setTextColor(
getColor(R.color.timeWorse)
)
- else -> behindTimeField.setTextColor(getColor(R.color.white))
+ else -> ahead2TimeField.setTextColor(getColor(R.color.white))
}
+
+ ahead2TyreField.text = context.ahead2.tyreTypeValue
+ ahead2TyreField.setTextColor(getColor(context.ahead2.tyreTyreColor))
+ ahead2TyreField.background =
+ if (context.ahead2.isTyresNew) getDrawable(R.color.tyreNew) else null
+ } else {
+ ahead2DriverField.text = "XX"
+ ahead2TimeField.text = "X:XX.XXX"
+ ahead2TimeField.setTextColor(getColor(R.color.white))
+ ahead2TyreField.text = "X"
+ ahead2TyreField.setTextColor(getColor(R.color.white))
+ ahead2TyreField.background = null
+ }
+ }
+
+ run {
+ val playerBestTimeField = findViewById(R.id.myBestValue)
+ val playerLastTimeField = findViewById(R.id.myTimeValue)
+ val playerTyreField = findViewById(R.id.myTyreValue)
+
+ if (context.player != null) {
+ playerBestTimeField.text = timeFormatter(context.player.bestLapTime)
+ playerLastTimeField.text = timeFormatter(context.player.lastLapTime)
+
+ if (context.player.lap < context.ahead?.lap ?: context.player.lap) {
+ playerLastTimeField.setTextColor(getColor(R.color.timeIrrelevant))
+ } else {
+ playerLastTimeField.setTextColor(getColor(R.color.white))
+ }
+
+ playerTyreField.text = context.player.tyreTypeValue
+ playerTyreField.setTextColor(getColor(context.player.tyreTyreColor))
+ playerTyreField.background =
+ if (context.player.isTyresNew) getDrawable(R.color.tyreNew) else null
+ } else {
+ playerBestTimeField.text = "X:XX.XXX"
+ playerLastTimeField.text = "X:XX.XXX"
+ playerTyreField.text = "X"
+ playerTyreField.setTextColor(getColor(R.color.white))
+ playerTyreField.background = null
+ }
+ }
+
+ run {
+ val behindDriverField = findViewById(R.id.behindDriverValue)
+ val behindTimeField = findViewById(R.id.behindTimeValue)
+ val behindTyreFiled = findViewById(R.id.behindTyreValue)
+
+ if (context.behind != null) {
+ behindDriverField.text =
+ context.behind.positionString + context.behind.driver.let { if (it != null) " ${it.driver.name}" else "" }
+ behindTimeField.text = timeFormatter(context.behind.lastLapTime)
+
+ if (context.behind.lap < context.player?.lap ?: context.behind.lap) {
+ behindTimeField.setTextColor(getColor(R.color.timeIrrelevant))
+ } else {
+ when {
+ context.behind.lastLapTime < context.player?.lastLapTime ?: Float.MIN_VALUE -> behindTimeField.setTextColor(
+ getColor(R.color.timeBetter)
+ )
+ context.behind.lastLapTime > context.player?.lastLapTime ?: Float.MAX_VALUE -> behindTimeField.setTextColor(
+ getColor(R.color.timeWorse)
+ )
+ else -> behindTimeField.setTextColor(getColor(R.color.white))
+ }
+ }
+
+ behindTyreFiled.text = context.behind.tyreTypeValue
+ behindTyreFiled.setTextColor(getColor(context.behind.tyreTyreColor))
+ behindTyreFiled.background =
+ if (context.behind.isTyresNew) getDrawable(R.color.tyreNew) else null
+
+ } else {
+ behindDriverField.text = "XX"
+ behindTimeField.text = "X:XX.XXX"
+ behindTimeField.setTextColor(getColor(R.color.white))
+ behindTyreFiled.text = "X"
+ behindTyreFiled.setTextColor(getColor(R.color.white))
+ behindTyreFiled.background = null
+ }
+ }
+
+ run {
+ val behind2DriverField = findViewById(R.id.behind2DriverValue)
+ val behind2TimeField = findViewById(R.id.behind2TimeValue)
+ val behind2TyreFiled = findViewById(R.id.behind2TyreValue)
+
+ if (context.behind2 != null) {
+ behind2DriverField.text =
+ context.behind2.positionString + context.behind2.driver.let { if (it != null) " ${it.driver.name}" else "" }
+ behind2TimeField.text = timeFormatter(context.behind2.lastLapTime)
+
+ if (context.behind2.lap < context.player?.lap ?: context.behind2.lap) {
+ behind2TimeField.setTextColor(getColor(R.color.timeIrrelevant))
+ } else {
+ when {
+ context.behind2.lastLapTime < context.player?.lastLapTime ?: Float.MIN_VALUE -> behind2TimeField.setTextColor(
+ getColor(R.color.timeBetter)
+ )
+ context.behind2.lastLapTime > context.player?.lastLapTime ?: Float.MAX_VALUE -> behind2TimeField.setTextColor(
+ getColor(R.color.timeWorse)
+ )
+ else -> behind2TimeField.setTextColor(getColor(R.color.white))
+ }
+ }
+
+ behind2TyreFiled.text = context.behind2.tyreTypeValue
+ behind2TyreFiled.setTextColor(getColor(context.behind2.tyreTyreColor))
+ behind2TyreFiled.background =
+ if (context.behind2.isTyresNew) getDrawable(R.color.tyreNew) else null
+
+ } else {
+ behind2DriverField.text = "XX"
+ behind2TimeField.text = "X:XX.XXX"
+ behind2TimeField.setTextColor(getColor(R.color.white))
+ behind2TyreFiled.text = "X"
+ behind2TyreFiled.setTextColor(getColor(R.color.white))
+ behind2TyreFiled.background = null
}
- } else {
- behindDeltaField.text = "X"
- behindTimeField.text = "X"
- behindTimeField.setTextColor(getColor(R.color.white))
}
}
),
LiveDataField(
"bestTime",
- 0.0f,
+ BestLapField(
+ competitorId = -1,
+ driver = null,
+ bestLapTime = 0.0f
+ ),
{ data, packet ->
packet.asType {
- return@LiveDataField it.data.items.filter { it.bestLapTime > 0 }
- .minOfOrNull { it.bestLapTime } ?: 0.0f
+ val bestLapTime = it.data.items.filter { it.bestLapTime > 0 }
+ .minOfOrNull { it.bestLapTime }
+ if (bestLapTime != null && (data.competitorId == -1 || data.bestLapTime > bestLapTime)) {
+ return@LiveDataField data.copy(
+ competitorId = -1,
+ driver = null,
+ bestLapTime = it.data.items.filter { it.bestLapTime > 0 }
+ .minOfOrNull { it.bestLapTime } ?: 0.0f
+ )
+ }
+ }
+ packet.asType {
+ if (it.data.eventType == EventDetails.Type.SessionStarted) {
+ return@LiveDataField BestLapField(
+ competitorId = -1,
+ driver = null,
+ bestLapTime = 0.0f
+ )
+ }
+ it.data.asType { event ->
+ val cid = event.vehicleIdx.toInt()
+ return@LiveDataField data.copy(
+ competitorId = cid,
+ driver = if (data.competitorId == cid) data.driver else null,
+ bestLapTime = event.lapTime
+ )
+ }
+ }
+ packet.asType {
+ if (data.competitorId >= 0 && data.competitorId < it.data.items.size) {
+ return@LiveDataField data.copy(
+ driver = it.data.items[data.competitorId].driver
+ )
+ }
}
return@LiveDataField data
},
{
- (findViewById(R.id.bestSessionTime) as TextView).text = timeFormatter(it)
+ (findViewById(R.id.bestSessionTime) as TextView).text =
+ timeFormatter(it.bestLapTime) + " " + (it.driver?.name ?: "")
}
),
LiveDataField(
"tyres",
TypesField(
- tyreFL = TyreState(0, 0, 0),
- tyreFR = TyreState(0, 0, 0),
- tyreRL = TyreState(0, 0, 0),
- tyreRR = TyreState(0, 0, 0)
+ tyreFL = TyreStateField(0, 0, 0),
+ tyreFR = TyreStateField(0, 0, 0),
+ tyreRL = TyreStateField(0, 0, 0),
+ tyreRR = TyreStateField(0, 0, 0)
),
{ data, packet ->
packet.asType {
@@ -614,32 +923,40 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- findViewById(R.id.wearFLValue).background =
- getDrawable(defineWearColor(it.tyreFL.wear))
- findViewById(R.id.wearFRValue).background =
- getDrawable(defineWearColor(it.tyreFR.wear))
- findViewById(R.id.wearRLValue).background =
- getDrawable(defineWearColor(it.tyreRL.wear))
- findViewById(R.id.wearRRValue).background =
- getDrawable(defineWearColor(it.tyreRR.wear))
-
- findViewById(R.id.surfaceFLValue).background =
- getDrawable(defineTempColor(it.tyreFL.outerTemperature))
- findViewById(R.id.surfaceFRValue).background =
- getDrawable(defineTempColor(it.tyreFR.outerTemperature))
- findViewById(R.id.surfaceRLValue).background =
- getDrawable(defineTempColor(it.tyreRL.outerTemperature))
- findViewById(R.id.surfaceRRValue).background =
- getDrawable(defineTempColor(it.tyreRR.outerTemperature))
-
- findViewById(R.id.innerFLValue).background =
- getDrawable(defineTempColor(it.tyreFL.innerTemperature))
- findViewById(R.id.innerFRValue).background =
- getDrawable(defineTempColor(it.tyreFR.innerTemperature))
- findViewById(R.id.innerRLValue).background =
- getDrawable(defineTempColor(it.tyreRL.innerTemperature))
- findViewById(R.id.innerRRValue).background =
- getDrawable(defineTempColor(it.tyreRR.innerTemperature))
+ findViewById(R.id.wearFLValue).also { view ->
+ view.background = getDrawable(it.tyreFL.wearColor)
+ view.text = it.tyreFL.wearValue
+ }
+ findViewById(R.id.wearFRValue).also { view ->
+ view.background = getDrawable(it.tyreFR.wearColor)
+ view.text = it.tyreFR.wearValue
+ }
+ findViewById(R.id.wearRLValue).also { view ->
+ view.background = getDrawable(it.tyreRL.wearColor)
+ view.text = it.tyreRL.wearValue
+ }
+ findViewById(R.id.wearRRValue).also { view ->
+ view.background = getDrawable(it.tyreRR.wearColor)
+ view.text = it.tyreRR.wearValue
+ }
+
+ findViewById(R.id.surfaceFLValue).background =
+ getDrawable(it.tyreFL.outerTemperatureColor)
+ findViewById(R.id.surfaceFRValue).background =
+ getDrawable(it.tyreFR.outerTemperatureColor)
+ findViewById(R.id.surfaceRLValue).background =
+ getDrawable(it.tyreRL.outerTemperatureColor)
+ findViewById(R.id.surfaceRRValue).background =
+ getDrawable(it.tyreRR.outerTemperatureColor)
+
+ findViewById(R.id.innerFLValue).background =
+ getDrawable(it.tyreFL.innerTemperatureColor)
+ findViewById(R.id.innerFRValue).background =
+ getDrawable(it.tyreFR.innerTemperatureColor)
+ findViewById(R.id.innerRLValue).background =
+ getDrawable(it.tyreRL.innerTemperatureColor)
+ findViewById(R.id.innerRRValue).background =
+ getDrawable(it.tyreRR.innerTemperatureColor)
}
),
LiveDataField(
@@ -655,8 +972,8 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val frontLeftWingField = findViewById(R.id.frontWingLeftDamage) as TextView
- val frontRightWingField = findViewById(R.id.frontWingRightDamage) as TextView
+ val frontLeftWingField = findViewById(R.id.frontWingLeftDamage)
+ val frontRightWingField = findViewById(R.id.frontWingRightDamage)
if (it[0] > 0) {
frontLeftWingField.text = it[0].toString()
frontLeftWingField.background = getDrawable(R.color.warn)
@@ -683,7 +1000,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val rearWingField = findViewById(R.id.rearWingDamage) as TextView
+ val rearWingField = findViewById(R.id.rearWingDamage)
if (it > 0) {
rearWingField.text = it.toString()
rearWingField.background = getDrawable(R.color.warn)
@@ -703,7 +1020,7 @@ val LiveDataFields = listOf>(
return@LiveDataField data
},
{
- val engineField = findViewById(R.id.engineTempValue) as TextView
+ val engineField = findViewById(R.id.engineTempValue)
engineField.text = it.toString()
when {
it >= 130 -> {
@@ -745,8 +1062,10 @@ val LiveDataFields = listOf>(
0,
{ data, _ -> data + 1 },
{
- val debugField = findViewById(R.id.debugFrameCount) as TextView
- debugField.text = it.toString()
+ if (it % 100 == 0) {
+ val debugField = findViewById(R.id.debugFrameCount) as TextView
+ debugField.text = it.toString()
+ }
}
)
)
\ No newline at end of file
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/model/TyreCompound.kt b/app/src/main/java/ru/n1ks/f1dashboard/model/TyreCompound.kt
index 6abc6d4..a86a689 100644
--- a/app/src/main/java/ru/n1ks/f1dashboard/model/TyreCompound.kt
+++ b/app/src/main/java/ru/n1ks/f1dashboard/model/TyreCompound.kt
@@ -1,11 +1,30 @@
package ru.n1ks.f1dashboard.model
-enum class TyreCompound {
- C1, C2, C3, C4, C5, Inter, Wet, DryClassic, WetClassic, SuperSoftF2, SoftF2, MediumF2, HardF2, WetF2, Soft, Medium, Hard;
+import ru.n1ks.f1dashboard.R
+
+enum class TyreCompound(val char: Char, val color: Int) {
+ X('X', R.color.white),
+ C1('⑴', R.color.tyreH),
+ C2('⑵', R.color.tyreM),
+ C3('⑶', R.color.tyreS),
+ C4('⑷', R.color.tyreSS),
+ C5('⑸', R.color.tyreUS),
+ Inter('Ⓘ', R.color.tyreInter),
+ Wet('Ⓦ', R.color.tyreWet),
+ DryClassic('ⓓ', R.color.tyreDry),
+ WetClassic('ⓦ', R.color.tyreWet),
+ SuperSoftF2('Ⓠ', R.color.tyreUS),
+ SoftF2('Ⓢ', R.color.tyreSS),
+ MediumF2('Ⓜ', R.color.tyreS),
+ HardF2('Ⓗ', R.color.tyreM),
+ WetF2('Ⓦ', R.color.tyreWet),
+ Soft('Ⓢ', R.color.tyreSS),
+ Medium('Ⓜ', R.color.tyreS),
+ Hard('Ⓗ', R.color.tyreM),
+ ;
- @ExperimentalUnsignedTypes
companion object {
- fun defineActualByCode(code: UByte) = when (code.toInt()) {
+ fun defineActualByCode(code: Byte) = when (code.toInt()) {
16 -> C5
17 -> C4
18 -> C3
@@ -20,10 +39,10 @@ enum class TyreCompound {
13 -> MediumF2
14 -> HardF2
15 -> WetF2
- else -> throw IllegalStateException("unknown code $code")
+ else -> X
}
- fun defineVisualByCode(code: UByte) = when (code.toInt()) {
+ fun defineVisualByCode(code: Byte) = when (code.toInt()) {
16 -> Soft
17 -> Medium
18 -> Hard
@@ -36,7 +55,7 @@ enum class TyreCompound {
13 -> MediumF2
14 -> HardF2
15 -> WetF2
- else -> throw IllegalStateException("unknown code $code")
+ else -> X
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/model/mapper.kt b/app/src/main/java/ru/n1ks/f1dashboard/model/mapper.kt
index 398fc2f..ee618b4 100644
--- a/app/src/main/java/ru/n1ks/f1dashboard/model/mapper.kt
+++ b/app/src/main/java/ru/n1ks/f1dashboard/model/mapper.kt
@@ -3,7 +3,6 @@ package ru.n1ks.f1dashboard.model
import java.nio.ByteBuffer
import java.nio.ByteOrder
-@ExperimentalUnsignedTypes
object TelemetryPacketDeserializer {
private const val CarCount = 22
@@ -31,6 +30,9 @@ object TelemetryPacketDeserializer {
PackageType.CarSetupsType.id -> {
TelemetryPacket(header, mapCarSetupDataPacket(buffer))
}
+ PackageType.EventType.id -> {
+ TelemetryPacket(header, mapEventDataPacket(buffer))
+ }
else -> {
TelemetryPacket(header, EmptyData)
}
@@ -39,48 +41,48 @@ object TelemetryPacketDeserializer {
private fun mapHeader(buffer: ByteBuffer): TelemetryHeader =
TelemetryHeader(
- packetFormat = buffer.short.toUShort(),
- gameMajorVersion = buffer.get().toUByte(),
- gameMinorVersion = buffer.get().toUByte(),
- packetVersion = buffer.get().toUByte(),
- packetTypeId = buffer.get().toUByte(),
- sessionId = buffer.long.toULong(),
+ packetFormat = buffer.short,
+ gameMajorVersion = buffer.get(),
+ gameMinorVersion = buffer.get(),
+ packetVersion = buffer.get(),
+ packetTypeId = buffer.get(),
+ sessionId = buffer.long,
sessionTimestamp = buffer.float,
- frameId = buffer.int.toUInt(),
+ frameId = buffer.int,
playerCarIndex = buffer.get().toInt(),
- secondaryPlayerCarIndex = buffer.get().toUByte()
+ secondaryPlayerCarIndex = buffer.get()
)
private fun mapCarTelemetryData(buffer: ByteBuffer): CarTelemetryData =
CarTelemetryData(
- speed = buffer.short.toUShort(),
+ speed = buffer.short,
throttle = buffer.float,
steer = buffer.float,
brake = buffer.float,
- clutch = buffer.get().toUByte(),
+ clutch = buffer.get(),
gear = buffer.get(),
- engineRPM = buffer.short.toUShort(),
- _drs = buffer.get().toUByte(),
- revLightsPercent = buffer.get().toUByte(),
+ engineRPM = buffer.short,
+ _drs = buffer.get(),
+ revLightsPercent = buffer.get(),
_brakesTemperature = arrayOf(
- buffer.short.toUShort(),
- buffer.short.toUShort(),
- buffer.short.toUShort(),
- buffer.short.toUShort()
+ buffer.short,
+ buffer.short,
+ buffer.short,
+ buffer.short
),
_tyresSurfaceTemperature = arrayOf(
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte()
+ buffer.get(),
+ buffer.get(),
+ buffer.get(),
+ buffer.get()
),
_tyresInnerTemperature = arrayOf(
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte()
+ buffer.get(),
+ buffer.get(),
+ buffer.get(),
+ buffer.get()
),
- engineTemperature = buffer.short.toUShort(),
+ engineTemperature = buffer.short,
_tyresPressure = arrayOf(
buffer.float,
buffer.float,
@@ -88,10 +90,10 @@ object TelemetryPacketDeserializer {
buffer.float
),
_surfaceType = arrayOf(
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte()
+ buffer.get(),
+ buffer.get(),
+ buffer.get(),
+ buffer.get()
)
)
@@ -100,41 +102,41 @@ object TelemetryPacketDeserializer {
CarTelemetryDataPacket(
items = generateSequence(0) { it + 1 }.take(CarCount)
.map { mapCarTelemetryData(buffer) }.toList(),
- buttonStatus = buffer.int.toUInt(),
- mfdPanelIndex = buffer.get().toUByte(),
- mfdPanelIndexSecondaryPlayer = buffer.get().toUByte(),
- suggestedGear = buffer.get().toUByte()
+ buttonStatus = buffer.int,
+ mfdPanelIndex = buffer.get(),
+ mfdPanelIndexSecondaryPlayer = buffer.get(),
+ suggestedGear = buffer.get()
)
private fun mapLapData(buffer: ByteBuffer): LapData =
LapData(
lastLapTime = buffer.float,
currentLapTime = buffer.float,
- sector1TimeInMS = buffer.short.toUShort(),
- sector2TimeInMS = buffer.short.toUShort(),
+ sector1TimeInMS = buffer.short,
+ sector2TimeInMS = buffer.short,
bestLapTime = buffer.float,
- bestLapNum = buffer.get().toUByte(),
- bestLapSector1TimeInMS = buffer.short.toUShort(),
- bestLapSector2TimeInMS = buffer.short.toUShort(),
- bestLapSector3TimeInMS = buffer.short.toUShort(),
- bestOverallSector1TimeInMS = buffer.short.toUShort(),
- bestOverallSector1LapNum = buffer.get().toUByte(),
- bestOverallSector2TimeInMS = buffer.short.toUShort(),
- bestOverallSector2LapNum = buffer.get().toUByte(),
- bestOverallSector3TimeInMS = buffer.short.toUShort(),
- bestOverallSector3LapNum = buffer.get().toUByte(),
+ bestLapNum = buffer.get(),
+ bestLapSector1TimeInMS = buffer.short,
+ bestLapSector2TimeInMS = buffer.short,
+ bestLapSector3TimeInMS = buffer.short,
+ bestOverallSector1TimeInMS = buffer.short,
+ bestOverallSector1LapNum = buffer.get(),
+ bestOverallSector2TimeInMS = buffer.short,
+ bestOverallSector2LapNum = buffer.get(),
+ bestOverallSector3TimeInMS = buffer.short,
+ bestOverallSector3LapNum = buffer.get(),
lapDistance = buffer.float,
totalDistance = buffer.float,
safetyCarDelta = buffer.float,
- carPosition = buffer.get().toUByte(),
- currentLapNum = buffer.get().toUByte(),
- _pitStatus = buffer.get().toUByte(),
- sector = buffer.get().toUByte(),
- _currentLapInvalid = buffer.get().toUByte(),
- penalties = buffer.get().toUByte(),
- gridPosition = buffer.get().toUByte(),
- _driverStatus = buffer.get().toUByte(),
- _resultStatus = buffer.get().toUByte()
+ carPosition = buffer.get(),
+ currentLapNum = buffer.get(),
+ _pitStatus = buffer.get(),
+ sector = buffer.get(),
+ _currentLapInvalid = buffer.get(),
+ penalties = buffer.get(),
+ gridPosition = buffer.get(),
+ _driverStatus = buffer.get(),
+ _resultStatus = buffer.get()
)
private fun mapLapDataPacket(buffer: ByteBuffer): LapDataPacket =
@@ -145,43 +147,43 @@ object TelemetryPacketDeserializer {
private fun mapCarStatusData(buffer: ByteBuffer): CarStatusData =
CarStatusData(
- tractionControl = buffer.get().toUByte(),
- antiLockBrakes = buffer.get().toUByte(),
- _fuelMix = buffer.get().toUByte(),
- frontBrakeBias = buffer.get().toUByte(),
- _pitLimiterStatus = buffer.get().toUByte(),
+ tractionControl = buffer.get(),
+ antiLockBrakes = buffer.get(),
+ _fuelMix = buffer.get(),
+ frontBrakeBias = buffer.get(),
+ _pitLimiterStatus = buffer.get(),
fuelInTank = buffer.float,
fuelCapacity = buffer.float,
fuelRemainingLaps = buffer.float,
- maxRPM = buffer.short.toUShort(),
- idleRPM = buffer.short.toUShort(),
- maxGears = buffer.get().toUByte(),
- _drsAllowed = buffer.get().toUByte(),
- drsActivationDistance = buffer.short.toUShort(),
+ maxRPM = buffer.short,
+ idleRPM = buffer.short,
+ maxGears = buffer.get(),
+ _drsAllowed = buffer.get(),
+ drsActivationDistance = buffer.short,
_tyresWear = arrayOf(
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte()
+ buffer.get(),
+ buffer.get(),
+ buffer.get(),
+ buffer.get()
),
- _actualTyreCompound = buffer.get().toUByte(),
- _visualTyreCompound = buffer.get().toUByte(),
- tyresAgeLaps = buffer.get().toUByte(),
+ _actualTyreCompound = buffer.get(),
+ _visualTyreCompound = buffer.get(),
+ tyresAgeLaps = buffer.get(),
_tyresDamage = arrayOf(
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte()
+ buffer.get(),
+ buffer.get(),
+ buffer.get(),
+ buffer.get()
),
- frontLeftWingDamage = buffer.get().toUByte(),
- frontRightWingDamage = buffer.get().toUByte(),
- rearWingDamage = buffer.get().toUByte(),
- _drsFault = buffer.get().toUByte(),
- engineDamage = buffer.get().toUByte(),
- gearBoxDamage = buffer.get().toUByte(),
+ frontLeftWingDamage = buffer.get(),
+ frontRightWingDamage = buffer.get(),
+ rearWingDamage = buffer.get(),
+ _drsFault = buffer.get(),
+ engineDamage = buffer.get(),
+ gearBoxDamage = buffer.get(),
_vehicleFiaFlags = buffer.get(),
ersStoreEnergy = buffer.float,
- ersDeployMode = buffer.get().toUByte(),
+ ersDeployMode = buffer.get(),
ersHarvestedThisLapMGUK = buffer.float,
ersHarvestedThisLapMGUH = buffer.float,
ersDeployedThisLap = buffer.float
@@ -195,37 +197,37 @@ object TelemetryPacketDeserializer {
private fun mapSessionData(buffer: ByteBuffer): SessionDataPacket =
SessionDataPacket(
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get(),
- buffer.get().toUByte(),
- buffer.short.toUShort(),
- buffer.get().toUByte(),
- buffer.get(),
- buffer.get().toUByte(),
- buffer.short.toUShort(),
- buffer.short.toUShort(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte()
+ weather = buffer.get(),
+ trackTemperature = buffer.get(),
+ airTemperature = buffer.get(),
+ totalLaps = buffer.get(),
+ trackLength = buffer.short,
+ sessionType = buffer.get(),
+ trackId = buffer.get(),
+ formula = buffer.get(),
+ sessionTimeLeft = buffer.short,
+ sessionDuration = buffer.short,
+ pitSpeedLimit = buffer.get(),
+ gamePaused = buffer.get(),
+ isSpectating = buffer.get(),
+ spectatorCarIndex = buffer.get(),
+ sliProNativeSupport = buffer.get()
)
private fun mapParticipantData(buffer: ByteBuffer): ParticipantData {
val participantData = ParticipantData(
- aiControlled = buffer.get().toUByte(),
- _driverId = buffer.get().toUByte(),
- teamId = buffer.get().toUByte(),
- raceNumber = buffer.get().toUByte(),
- nationality = buffer.get().toUByte()
+ aiControlled = buffer.get(),
+ _driverId = buffer.get(),
+ teamId = buffer.get(),
+ raceNumber = buffer.get(),
+ nationality = buffer.get()
)
buffer.position(buffer.position() + 49)
return participantData
}
private fun mapParticipantDataPacket(buffer: ByteBuffer): ParticipantDataPacket {
- val count = buffer.get().toUByte()
+ val count = buffer.get()
return ParticipantDataPacket(
generateSequence(0) { it + 1 }.take(count.toInt())
.map { mapParticipantData(buffer) }.toList()
@@ -234,32 +236,80 @@ object TelemetryPacketDeserializer {
private fun mapCarSetupData(buffer: ByteBuffer): CarSetupData =
CarSetupData(
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.float,
- buffer.float,
- buffer.float,
- buffer.float,
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.get().toUByte(),
- buffer.float,
- buffer.float,
- buffer.float,
- buffer.float,
- buffer.get().toUByte(),
- buffer.float
+ frontWing = buffer.get(),
+ rearWing = buffer.get(),
+ onThrottle = buffer.get(),
+ offThrottle = buffer.get(),
+ frontCamber = buffer.float,
+ rearCamber = buffer.float,
+ frontToe = buffer.float,
+ rearToe = buffer.float,
+ frontSuspension = buffer.get(),
+ rearSuspension = buffer.get(),
+ frontAntiRollBar = buffer.get(),
+ rearAntiRollBar = buffer.get(),
+ frontSuspensionHeight = buffer.get(),
+ rearSuspensionHeight = buffer.get(),
+ brakePressure = buffer.get(),
+ brakeBias = buffer.get(),
+ rearLeftTyrePressure = buffer.float,
+ rearRightTyrePressure = buffer.float,
+ frontLeftTyrePressure = buffer.float,
+ frontRightTyrePressure = buffer.float,
+ ballast = buffer.get(),
+ fuelLoad = buffer.float
)
private fun mapCarSetupDataPacket(buffer: ByteBuffer): CarSetupDataPacket =
CarSetupDataPacket(
- items = generateSequence(0) { it + 1 }.take(CarCount).map { mapCarSetupData(buffer) }.toList()
+ items = generateSequence(0) { it + 1 }.take(CarCount).map { mapCarSetupData(buffer) }
+ .toList()
+ )
+
+ private fun mapEventData(buffer: ByteBuffer, eventType: EventDetails.Type): EventDetails {
+ return when (eventType) {
+ EventDetails.Type.SessionStarted -> EmptyEventData // Sent when the session starts
+ EventDetails.Type.SessionEnded -> EmptyEventData // Sent when the session ends
+ EventDetails.Type.FastestLap -> FastestLapData(
+ vehicleIdx = buffer.get(),
+ lapTime = buffer.float
+ ) // When a driver achieves the fastest lap
+ EventDetails.Type.Retirement -> Retirement(
+ vehicleIdx = buffer.get()
+ ) // When a driver retires
+ EventDetails.Type.DRSEnabled -> EmptyEventData // Race control have enabled DRS
+ EventDetails.Type.DRSDisabled -> EmptyEventData // Race control have disabled DRS
+ EventDetails.Type.TeammateInPits -> TeamMateInPits(
+ vehicleIdx = buffer.get()
+ ) // Your team mate has entered the pits
+ EventDetails.Type.ChequeredFlag -> EmptyEventData // The chequered flag has been waved
+ EventDetails.Type.RaceWinner -> RaceWinner(
+ vehicleIdx = buffer.get()
+ ) // The race winner is announced
+ EventDetails.Type.PenaltyIssued -> Penalty(
+ penaltyType = buffer.get(),
+ infringementType = buffer.get(),
+ vehicleIdx = buffer.get(),
+ otherVehicleIdx = buffer.get(),
+ time = buffer.get(),
+ lapNum = buffer.get(),
+ placesGained = buffer.get()
+ ) // A penalty has been issued – details in event
+ EventDetails.Type.SpeedTrap -> SpeedTrap(
+ vehicleIdx = buffer.get(),
+ speed = buffer.float
+ ) // Speed trap has been triggered by fastest speed
+ else -> EmptyEventData
+ }
+ }
+
+ private fun mapEventDataPacket(buffer: ByteBuffer): EventDataPacket {
+ val eventTypeBytes = ByteArray(4)
+ buffer.get(eventTypeBytes)
+ val eventType = EventDetails.Type.getByCode(String(eventTypeBytes))
+ return EventDataPacket(
+ eventType = eventType,
+ details = mapEventData(buffer, eventType)
)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/model/packets.kt b/app/src/main/java/ru/n1ks/f1dashboard/model/packets.kt
index 4317b40..8c9856d 100644
--- a/app/src/main/java/ru/n1ks/f1dashboard/model/packets.kt
+++ b/app/src/main/java/ru/n1ks/f1dashboard/model/packets.kt
@@ -1,38 +1,35 @@
package ru.n1ks.f1dashboard.model
-import ru.n1ks.f1dashboard.UByteOne
+import ru.n1ks.f1dashboard.Bytes
interface TelemetryData
-@ExperimentalUnsignedTypes
-enum class PackageType(val id: UByte) {
- MotionType(0u),
- SessionType(1u),
- LapDataType(2u),
- EventType(3u),
- ParticipantsType(4u),
- CarSetupsType(5u),
- CarTelemetryType(6u),
- CarStatusType(7u),
- FinalClassificationType(8u),
- LobbyInfoType(9u)
+enum class PackageType(val id: Byte) {
+ MotionType(0),
+ SessionType(1),
+ LapDataType(2),
+ EventType(3),
+ ParticipantsType(4),
+ CarSetupsType(5),
+ CarTelemetryType(6),
+ CarStatusType(7),
+ FinalClassificationType(8),
+ LobbyInfoType(9)
}
-@ExperimentalUnsignedTypes
data class TelemetryHeader(
- val packetFormat: UShort,
- val gameMajorVersion: UByte,
- val gameMinorVersion: UByte,
- val packetVersion: UByte,
- val packetTypeId: UByte,
- val sessionId: ULong,
+ val packetFormat: Short,
+ val gameMajorVersion: Byte,
+ val gameMinorVersion: Byte,
+ val packetVersion: Byte,
+ val packetTypeId: Byte,
+ val sessionId: Long,
val sessionTimestamp: Float,
- val frameId: UInt,
+ val frameId: Int,
val playerCarIndex: Int,
- val secondaryPlayerCarIndex: UByte
+ val secondaryPlayerCarIndex: Byte
)
-@ExperimentalUnsignedTypes
data class TelemetryPacket(
val header: TelemetryHeader,
val data: T
@@ -48,10 +45,9 @@ data class TelemetryPacket(
object EmptyData : TelemetryData
-@ExperimentalUnsignedTypes
class CarTelemetryData(
/** Speed of car in kilometres per hour */
- val speed: UShort,
+ val speed: Short,
/** Amount of throttle applied (0.0 to 1.0) */
val throttle: Float,
/** Steering (-1.0 (full lock left) to 1.0 (full lock right)) */
@@ -59,42 +55,42 @@ class CarTelemetryData(
/** Amount of brake applied (0.0 to 1.0) */
val brake: Float,
/** Amount of clutch applied (0 to 100) */
- val clutch: UByte,
+ val clutch: Byte,
/** Gear selected (1-8, N=0, R=-1) */
val gear: Byte,
/** Engine RPM */
- val engineRPM: UShort,
+ val engineRPM: Short,
/** 0 = off, 1 = on */
- private val _drs: UByte,
+ private val _drs: Byte,
/** Rev lights indicator (percentage) */
- val revLightsPercent: UByte,
+ val revLightsPercent: Byte,
/** Brakes temperature (celsius) */
- private val _brakesTemperature: Array,
+ private val _brakesTemperature: Array,
/** Tyres surface temperature (celsius) */
- private val _tyresSurfaceTemperature: Array,
+ private val _tyresSurfaceTemperature: Array,
/** Tyres inner temperature (celsius) */
- private val _tyresInnerTemperature: Array,
+ private val _tyresInnerTemperature: Array,
/** Engine temperature (celsius) */
- val engineTemperature: UShort,
+ val engineTemperature: Short,
/** Tyres pressure (PSI) */
private val _tyresPressure: Array,
/** Driving surface, see appendices */
- private val _surfaceType: Array
+ private val _surfaceType: Array
) {
val drs: Boolean
- get() = _drs == UByteOne
+ get() = _drs == Bytes.One
- val brakesTemperatureRL: UShort
+ val brakesTemperatureRL: Short
get() = _brakesTemperature[0]
- val brakesTemperatureRR: UShort
+ val brakesTemperatureRR: Short
get() = _brakesTemperature[1]
- val brakesTemperatureFL: UShort
+ val brakesTemperatureFL: Short
get() = _brakesTemperature[2]
- val brakesTemperatureFR: UShort
+ val brakesTemperatureFR: Short
get() = _brakesTemperature[3]
val tyresSurfaceTemperatureRL: Int
@@ -133,65 +129,63 @@ class CarTelemetryData(
val tyresPressureFR: Float
get() = _tyresPressure[3]
- val surfaceTypeRL: UByte
+ val surfaceTypeRL: Byte
get() = _surfaceType[0]
- val surfaceTypeRR: UByte
+ val surfaceTypeRR: Byte
get() = _surfaceType[1]
- val surfaceTypeFL: UByte
+ val surfaceTypeFL: Byte
get() = _surfaceType[2]
- val surfaceTypeFR: UByte
+ val surfaceTypeFR: Byte
get() = _surfaceType[3]
}
-@ExperimentalUnsignedTypes
class CarTelemetryDataPacket(
val items: List,
/** Bit flags specifying which buttons are being pressed, currently - see appendices */
- val buttonStatus: UInt,
+ val buttonStatus: Int,
/** Index of MFD panel open - 255 = MFD closed: Single player, race – 0 = Car setup, 1 = Pits, 2 = Damage, 3 = Engine, 4 = Temperatures. May vary depending on game mode */
- val mfdPanelIndex: UByte,
+ val mfdPanelIndex: Byte,
/** See above */
- val mfdPanelIndexSecondaryPlayer: UByte,
+ val mfdPanelIndexSecondaryPlayer: Byte,
/** Suggested gear for the player (1-8), 0 if no gear suggested */
- val suggestedGear: UByte
+ val suggestedGear: Byte
) : TelemetryData
-@ExperimentalUnsignedTypes
class LapData(
/** Last lap time in seconds */
val lastLapTime: Float,
/** Current time around the lap in seconds */
val currentLapTime: Float,
/** Sector 1 time in milliseconds */
- val sector1TimeInMS: UShort,
+ val sector1TimeInMS: Short,
/** Sector 2 time in milliseconds */
- val sector2TimeInMS: UShort,
+ val sector2TimeInMS: Short,
/** Best lap time of the session in seconds */
val bestLapTime: Float,
/** Lap number best time achieved on */
- val bestLapNum: UByte,
+ val bestLapNum: Byte,
/** Sector 1 time of best lap in the session in milliseconds */
- val bestLapSector1TimeInMS: UShort,
+ val bestLapSector1TimeInMS: Short,
/** Sector 2 time of best lap in the session in milliseconds */
- val bestLapSector2TimeInMS: UShort,
+ val bestLapSector2TimeInMS: Short,
/** Sector 3 time of best lap in the session in milliseconds */
- val bestLapSector3TimeInMS: UShort,
+ val bestLapSector3TimeInMS: Short,
/** Best overall sector 1 time of the session in milliseconds */
- val bestOverallSector1TimeInMS: UShort,
+ val bestOverallSector1TimeInMS: Short,
/** Lap number best overall sector 1 time achieved on */
- val bestOverallSector1LapNum: UByte,
+ val bestOverallSector1LapNum: Byte,
/** Best overall sector 2 time of the session in milliseconds */
- val bestOverallSector2TimeInMS: UShort,
+ val bestOverallSector2TimeInMS: Short,
/** Lap number best overall sector 2 time achieved on */
- val bestOverallSector2LapNum: UByte,
+ val bestOverallSector2LapNum: Byte,
/** Best overall sector 3 time of the session in milliseconds */
- val bestOverallSector3TimeInMS: UShort,
+ val bestOverallSector3TimeInMS: Short,
/** Lap number best overall sector 3 time achieved on */
- val bestOverallSector3LapNum: UByte,
+ val bestOverallSector3LapNum: Byte,
/** Distance vehicle is around current lap in metres – could be negative if line hasn’t been crossed yet */
val lapDistance: Float,
/** Total distance travelled in session in metres – could be negative if line hasn’t been crossed yet */
@@ -199,23 +193,23 @@ class LapData(
/** Delta in seconds for safety car */
val safetyCarDelta: Float,
/** Car race position */
- val carPosition: UByte,
+ val carPosition: Byte,
/** Current lap number */
- val currentLapNum: UByte,
+ val currentLapNum: Byte,
/** 0 = none, 1 = pitting, 2 = in pit area */
- private val _pitStatus: UByte,
+ private val _pitStatus: Byte,
/** 0 = sector1, 1 = sector2, 2 = sector3 */
- val sector: UByte,
+ val sector: Byte,
/** Current lap invalid - 0 = valid, 1 = invalid */
- private val _currentLapInvalid: UByte,
+ private val _currentLapInvalid: Byte,
/** Accumulated time penalties in seconds to be added */
- val penalties: UByte,
+ val penalties: Byte,
/** Grid position the vehicle started the race in */
- val gridPosition: UByte,
+ val gridPosition: Byte,
/** Status of driver - 0 = in garage, 1 = flying lap 2 = in lap, 3 = out lap, 4 = on track */
- private val _driverStatus: UByte,
+ private val _driverStatus: Byte,
/** Result status - 0 = invalid, 1 = inactive, 2 = active 3 = finished, 4 = disqualified, 5 = not classified 6 = retired */
- private val _resultStatus: UByte
+ private val _resultStatus: Byte
) {
enum class PitStatus {
@@ -239,7 +233,7 @@ class LapData(
}
val currentLapInvalid: Boolean
- get() = _currentLapInvalid != UByte.MIN_VALUE
+ get() = _currentLapInvalid != Byte.MIN_VALUE
val driverStatus: DriverStatus
get() = when (_driverStatus.toInt()) {
@@ -264,23 +258,21 @@ class LapData(
}
}
-@ExperimentalUnsignedTypes
class LapDataPacket(
val items: List
) : TelemetryData
-@ExperimentalUnsignedTypes
class CarStatusData(
/** 0 (off) - 2 (high) */
- val tractionControl: UByte,
+ val tractionControl: Byte,
/** 0 (off) - 1 (on) */
- val antiLockBrakes: UByte,
+ val antiLockBrakes: Byte,
/** Fuel mix - 0 = lean, 1 = standard, 2 = rich, 3 = max */
- private val _fuelMix: UByte,
+ private val _fuelMix: Byte,
/** Front brake bias (percentage) */
- val frontBrakeBias: UByte,
+ val frontBrakeBias: Byte,
/** Pit limiter status - 0 = off, 1 = on */
- private val _pitLimiterStatus: UByte,
+ private val _pitLimiterStatus: Byte,
/** Current fuel mass */
val fuelInTank: Float,
/** Fuel capacity */
@@ -288,43 +280,43 @@ class CarStatusData(
/** Fuel remaining in terms of laps (value on MFD) */
val fuelRemainingLaps: Float,
/** Cars max RPM, point of rev limiter */
- val maxRPM: UShort,
+ val maxRPM: Short,
/** Cars idle RPM */
- val idleRPM: UShort,
+ val idleRPM: Short,
/** Maximum number of gears */
- val maxGears: UByte,
+ val maxGears: Byte,
/** 0 = not allowed, 1 = allowed, -1 = unknown */
- private val _drsAllowed: UByte,
- /** 0 = DRS not available, non-zero - DRS will be available in [X] metres */
- val drsActivationDistance: UShort,
+ private val _drsAllowed: Byte,
+ /** 0 = DRS not available, non-zero - DRS will be available in X metres */
+ val drsActivationDistance: Short,
/** Tyre wear percentage */
- private val _tyresWear: Array,
+ private val _tyresWear: Array,
/** F1 Modern - 16 = C5, 17 = C4, 18 = C3, 19 = C2, 20 = C1 7 = inter, 8 = wet F1 Classic - 9 = dry, 10 = wet F2 – 11 = super soft, 12 = soft, 13 = medium, 14 = hard 15 = wet */
- private val _actualTyreCompound: UByte,
+ private val _actualTyreCompound: Byte,
/** F1 visual (can be different from actual compound) 16 = soft, 17 = medium, 18 = hard, 7 = inter, 8 = wet F1 Classic – same as above F2 – same as above */
- private val _visualTyreCompound: UByte,
+ private val _visualTyreCompound: Byte,
/** Age in laps of the current set of tyres */
- val tyresAgeLaps: UByte,
+ val tyresAgeLaps: Byte,
/** Tyre damage (percentage) */
- private val _tyresDamage: Array,
+ private val _tyresDamage: Array,
/** Front left wing damage (percentage) */
- val frontLeftWingDamage: UByte,
+ val frontLeftWingDamage: Byte,
/** Front right wing damage (percentage) */
- val frontRightWingDamage: UByte,
+ val frontRightWingDamage: Byte,
/** Rear wing damage (percentage) */
- val rearWingDamage: UByte,
+ val rearWingDamage: Byte,
/** Indicator for DRS fault, 0 = OK, 1 = fault */
- private val _drsFault: UByte,
+ private val _drsFault: Byte,
/** Engine damage (percentage) */
- val engineDamage: UByte,
+ val engineDamage: Byte,
/** Gear box damage (percentage) */
- val gearBoxDamage: UByte,
+ val gearBoxDamage: Byte,
/** -1 = invalid/unknown, 0 = none, 1 = green 2 = blue, 3 = yellow, 4 = red */
private val _vehicleFiaFlags: Byte,
/** ERS energy store in Joules */
val ersStoreEnergy: Float,
/** ERS deployment mode, 0 = none, 1 = medium 2 = overtake, 3 = hotlap */
- val ersDeployMode: UByte,
+ val ersDeployMode: Byte,
/** ERS energy harvested this lap by MGU-K */
val ersHarvestedThisLapMGUK: Float,
/** ERS energy harvested this lap by MGU-H */
@@ -338,16 +330,16 @@ class CarStatusData(
}
val fuelMix: Int
- get() = (_fuelMix + UByteOne).toInt()
+ get() = _fuelMix + Bytes.One
val pitLimiter: Boolean
- get() = _pitLimiterStatus == UByteOne
+ get() = _pitLimiterStatus == Bytes.One
val drsAllowed: Boolean
- get() = _drsAllowed == UByteOne
+ get() = _drsAllowed == Bytes.One
val drsAvailable: Boolean
- get() = drsActivationDistance > 0u
+ get() = drsActivationDistance > 0
val tyresWearRL: Int
get() = _tyresWear[0].toInt()
@@ -367,20 +359,20 @@ class CarStatusData(
val visualTyreCompound: TyreCompound
get() = TyreCompound.defineVisualByCode(_visualTyreCompound)
- val tyresDamageRL: UByte
+ val tyresDamageRL: Byte
get() = _tyresDamage[0]
- val tyresDamageRR: UByte
+ val tyresDamageRR: Byte
get() = _tyresDamage[1]
- val tyresDamageFL: UByte
+ val tyresDamageFL: Byte
get() = _tyresDamage[2]
- val tyresDamageFR: UByte
+ val tyresDamageFR: Byte
get() = _tyresDamage[3]
val drsFault: Boolean
- get() = _drsFault == UByteOne
+ get() = _drsFault == Bytes.One
val fiaFlag: FiaFlag
get() = when (_vehicleFiaFlags.toInt()) {
@@ -394,7 +386,6 @@ class CarStatusData(
}
}
-@ExperimentalUnsignedTypes
class CarStatusDataPacket(
val items: List
) : TelemetryData
@@ -406,78 +397,75 @@ class MarshalZone(
val zoneFlag: Byte
)
-@ExperimentalUnsignedTypes
class WeatherForecastSample(
/** 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P, 5 = Q1 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ, 10 = R, 11 = R2 12 = Time Trial */
- val sessionType: UByte,
+ val sessionType: Byte,
/** Time in minutes the forecast is for */
- val timeOffset: UByte,
+ val timeOffset: Byte,
/** Weather - 0 = clear, 1 = light cloud, 2 = overcast 3 = light rain, 4 = heavy rain, 5 = storm */
- val weather: UByte,
+ val weather: Byte,
/** Track temp. in degrees celsius */
val trackTemperature: Byte,
/** Air temp. in degrees celsius */
val airTemperature: Byte
)
-@ExperimentalUnsignedTypes
class SessionDataPacket(
/** Weather - 0 = clear, 1 = light cloud, 2 = overcast 3 = light rain, 4 = heavy rain, 5 = storm */
- val weather: UByte,
+ val weather: Byte,
/** Track temp. in degrees celsius */
- val trackTemperature: UByte,
+ val trackTemperature: Byte,
/** Air temp. in degrees celsius */
val airTemperature: Byte,
/** Total number of laps in this race */
- val totalLaps: UByte,
+ val totalLaps: Byte,
/** Track length in metres */
- val trackLength: UShort,
+ val trackLength: Short,
/** 0 = unknown, 1 = P1, 2 = P2, 3 = P3, 4 = Short P 5 = Q1, 6 = Q2, 7 = Q3, 8 = Short Q, 9 = OSQ 10 = R, 11 = R2, 12 = Time Trial */
- val sessionType: UByte,
+ val sessionType: Byte,
/** -1 for unknown, 0-21 for tracks, see appendix */
val trackId: Byte,
/** Formula, 0 = F1 Modern, 1 = F1 Classic, 2 = F2, 3 = F1 Generic */
- val formula: UByte,
+ val formula: Byte,
/** Time left in session in seconds */
- val sessionTimeLeft: UShort,
+ val sessionTimeLeft: Short,
/** Session duration in seconds */
- val sessionDuration: UShort,
+ val sessionDuration: Short,
/** Pit speed limit in kilometres per hour */
- val pitSpeedLimit: UByte,
+ val pitSpeedLimit: Byte,
/** Whether the game is paused */
- val gamePaused: UByte,
+ val gamePaused: Byte,
/** Whether the player is spectating */
- val isSpectating: UByte,
+ val isSpectating: Byte,
/** Index of the car being spectated */
- val spectatorCarIndex: UByte,
+ val spectatorCarIndex: Byte,
/** SLI Pro support, 0 = inactive, 1 = active */
- val sliProNativeSupport: UByte
+ val sliProNativeSupport: Byte
// /** Number of marshal zones to follow */
-// val numMarshalZones: UByte,
+// val numMarshalZones: Byte,
// /** List of marshal zones – max 21 */
// val marshalZones: Array,
// /** 0 = no safety car, 1 = full safety car 2 = virtual safety car */
-// val safetyCarStatus: UByte,
+// val safetyCarStatus: Byte,
// /** 0 = offline, 1 = online */
-// val networkGame: UByte,
+// val networkGame: Byte,
// /** Number of weather samples to follow */
-// val numWeatherForecastSamples: UByte,
+// val numWeatherForecastSamples: Byte,
// /** Array of weather forecast samples */
// val weatherForecastSamples: Array
) : TelemetryData
-@ExperimentalUnsignedTypes
class ParticipantData(
/** Whether the vehicle is AI (1) or Human (0) controlled */
- val aiControlled: UByte,
+ val aiControlled: Byte,
/** Driver id - see appendix */
- private val _driverId: UByte,
+ private val _driverId: Byte,
/** Team id - see appendix */
- val teamId: UByte,
+ val teamId: Byte,
/** Race number of the car */
- val raceNumber: UByte,
+ val raceNumber: Byte,
/** Nationality of the driver */
- val nationality: UByte
+ val nationality: Byte
) {
enum class Driver(private val id: Int) {
@@ -540,38 +528,108 @@ class ParticipantData(
get() = Driver.getById(_driverId.toInt())
}
-@ExperimentalUnsignedTypes
class ParticipantDataPacket(
val items: List
) : TelemetryData
-@ExperimentalUnsignedTypes
class CarSetupData(
-/** Front wing aero */ val frontWing: UByte,
-/** Rear wing aero */ val rearWing: UByte,
-/** Differential adjustment on throttle (percentage) */ val onThrottle: UByte,
-/** Differential adjustment off throttle (percentage) */ val offThrottle: UByte,
+/** Front wing aero */ val frontWing: Byte,
+/** Rear wing aero */ val rearWing: Byte,
+/** Differential adjustment on throttle (percentage) */ val onThrottle: Byte,
+/** Differential adjustment off throttle (percentage) */ val offThrottle: Byte,
/** Front camber angle (suspension geometry) */ val frontCamber: Float,
/** Rear camber angle (suspension geometry) */ val rearCamber: Float,
/** Front toe angle (suspension geometry) */ val frontToe: Float,
/** Rear toe angle (suspension geometry) */ val rearToe: Float,
-/** Front suspension */ val frontSuspension: UByte,
-/** Rear suspension */ val rearSuspension: UByte,
-/** Front anti-roll bar */ val frontAntiRollBar: UByte,
-/** Front anti-roll bar */ val rearAntiRollBar: UByte,
-/** Front ride height */ val frontSuspensionHeight: UByte,
-/** Rear ride height */ val rearSuspensionHeight: UByte,
-/** Brake pressure (percentage) */ val brakePressure: UByte,
-/** Brake bias (percentage) */ val brakeBias: UByte,
+/** Front suspension */ val frontSuspension: Byte,
+/** Rear suspension */ val rearSuspension: Byte,
+/** Front anti-roll bar */ val frontAntiRollBar: Byte,
+/** Front anti-roll bar */ val rearAntiRollBar: Byte,
+/** Front ride height */ val frontSuspensionHeight: Byte,
+/** Rear ride height */ val rearSuspensionHeight: Byte,
+/** Brake pressure (percentage) */ val brakePressure: Byte,
+/** Brake bias (percentage) */ val brakeBias: Byte,
/** Rear left tyre pressure (PSI) */ val rearLeftTyrePressure: Float,
/** Rear right tyre pressure (PSI) */ val rearRightTyrePressure: Float,
/** Front left tyre pressure (PSI) */ val frontLeftTyrePressure: Float,
/** Front right tyre pressure (PSI) */ val frontRightTyrePressure: Float,
-/** Ballast */ val ballast: UByte,
+/** Ballast */ val ballast: Byte,
/** Fuel load */ val fuelLoad: Float
)
-@ExperimentalUnsignedTypes
class CarSetupDataPacket(
val items: List
-) : TelemetryData
\ No newline at end of file
+) : TelemetryData
+
+interface EventDetails {
+
+ enum class Type(private val code: String) {
+ SessionStarted("SSTA"),
+ SessionEnded("SEND"),
+ FastestLap("FTLP"),
+ Retirement("RTMT"),
+ DRSEnabled("DRSE"),
+ DRSDisabled("DRSD"),
+ TeammateInPits("TMPT"),
+ ChequeredFlag("CHQF"),
+ RaceWinner("RCWN"),
+ PenaltyIssued("PENA"),
+ SpeedTrap("SPTP"),
+ Empty("XXXX")
+ ;
+
+ companion object {
+
+ private val map = values().groupBy { it.code }.mapValues { it.value.first() }
+
+ fun getByCode(code: String): Type = map[code] ?: Empty
+ }
+ }
+}
+
+object EmptyEventData : EventDetails
+
+class FastestLapData(
+/** Vehicle index of car achieving fastest lap */ val vehicleIdx: Byte,
+/** Lap time is in seconds */ val lapTime: Float
+) : EventDetails
+
+class Retirement(
+/** Vehicle index of car retiring */ val vehicleIdx: Byte
+) : EventDetails
+
+class TeamMateInPits(
+/** Vehicle index of team mate */ val vehicleIdx: Byte
+) : EventDetails
+
+class RaceWinner(
+/** Vehicle index of the race winner */ val vehicleIdx: Byte
+) : EventDetails
+
+class Penalty(
+/** Penalty type – see Appendices */ val penaltyType: Byte,
+/** Infringement type – see Appendices */ val infringementType: Byte,
+/** Vehicle index of the car the penalty is applied to */ val vehicleIdx: Byte,
+/** Vehicle index of the other car involved */ val otherVehicleIdx: Byte,
+/** Time gained, or time spent doing action in seconds */ val time: Byte,
+/** Lap the penalty occurred on */ val lapNum: Byte,
+/** Number of places gained by this */ val placesGained: Byte
+) : EventDetails
+
+class SpeedTrap(
+/** Vehicle index of the vehicle triggering speed trap */ val vehicleIdx: Byte,
+/** Top speed achieved in kilometres per hour */ val speed: Float
+) : EventDetails
+
+class EventDataPacket(
+ /** Event string code, see below */ val eventType: EventDetails.Type,
+ /** Event details - should be interpreted differently for each type */ val details: EventDetails
+) : TelemetryData {
+
+ @Suppress("UNCHECKED_CAST")
+ inline fun asType(block: (T) -> Unit) {
+ if (this.details is T) {
+ block(this.details)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/n1ks/f1dashboard/reporting/CrashReportActivity.kt b/app/src/main/java/ru/n1ks/f1dashboard/reporting/CrashReportActivity.kt
new file mode 100644
index 0000000..fbc0316
--- /dev/null
+++ b/app/src/main/java/ru/n1ks/f1dashboard/reporting/CrashReportActivity.kt
@@ -0,0 +1,51 @@
+package ru.n1ks.f1dashboard.reporting
+
+import android.app.AlertDialog
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import org.acra.dialog.CrashReportDialogHelper
+import ru.n1ks.f1dashboard.R
+
+class CrashReportActivity : AppCompatActivity() {
+
+ private lateinit var helper: CrashReportDialogHelper
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ try {
+ helper = CrashReportDialogHelper(this, intent)
+ buildAndShowDialog()
+ } catch (e: IllegalArgumentException) {
+ finish()
+ }
+ }
+
+ private fun buildAndShowDialog() {
+ AlertDialog.Builder(this)
+ .setTitle(R.string.crashReportTitle)
+ .setMessage(R.string.crashReportText)
+ .setPositiveButton(R.string.crashReportSendCaption) { _, _ ->
+ startActivity(
+ Intent.createChooser(
+ Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(Intent.EXTRA_TEXT, helper.reportData.toJSON())
+ type = "text/plain"
+ },
+ getString(R.string.crashReportShareCaption)
+ )
+ )
+ helper.cancelReports()
+ finish()
+ }
+ .setNegativeButton(R.string.crashReportCancelCaption) { _, _ ->
+ helper.cancelReports()
+ finish()
+ }
+ .create().apply {
+ setCanceledOnTouchOutside(false)
+ show()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index a628767..d974889 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -17,8 +17,8 @@
+ android:layout_margin="8sp"
+ android:layout_weight="0.23">
+ android:layout_weight="0.65">
+ android:background="@color/ahead2">
+
+
+
+
+
+
+
+
+
+
+
+
@@ -282,26 +341,39 @@
+ android:textSize="@dimen/smartFortSize"
+ android:typeface="monospace" />
+
+
@@ -312,29 +384,88 @@
android:background="@color/behind">
+
+
+
+
+
+
+
+
+
+
+
+
@@ -439,10 +570,16 @@
@@ -450,7 +587,7 @@
@@ -511,17 +654,23 @@
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 05f1741..ac1cdd7 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -15,7 +15,9 @@
#467127
#854F00
#595959
+ #8E8E8E
#393939
+ #202020
#558B2F
#BD8D40
#C62828
@@ -37,4 +39,13 @@
#BFBFBF
#AAFFAD
#FFA59E
+ #4DC153
+ #0016B8
+ #FFFFFF
+ #AA84FF
+ #C62828
+ #F9EE25
+ #FFFFFF
+ #FF8F00
+ #5CFFCDD2
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 8280f75..dc07421 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,5 +1,6 @@
13pt
+ 10pt
18pt
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f72ce7b..a48da55 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,7 +2,11 @@
f1dashboard
F1 Dashboard
F1 Dashboard listen…
- Stop
+ It\'s crashed
+ You can send your crash info
+ Send
+ Cancel
+ Crash report
lap
fuel
ers
diff --git a/build.gradle b/build.gradle
index b55135a..ed2225e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = "1.4.21"
+ ext.kotlin_version = "1.5.10"
ext.rxkotlin_version = '2.1.0'
- ext.rxandroid_version = '2.0.1'
+ ext.rxandroid_version = '2.1.1'
+ ext.acra_version = '5.8.2'
+
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.3'
+ classpath 'com.android.tools.build:gradle:4.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@@ -19,7 +21,7 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 11d5f39..d378256 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755