diff --git a/app/build.gradle b/app/build.gradle index e39faca8..9c164133 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -118,13 +118,13 @@ dependencies { testImplementation 'junit:junit:4.13.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.ezylang:EvalEx:3.0.5' + implementation 'com.ezylang:EvalEx:3.1.0' implementation 'androidx.annotation:annotation:1.7.1' implementation "androidx.datastore:datastore:1.0.0" implementation 'com.google.protobuf:protobuf-java-util:3.25.1' implementation 'rongi.rotate-layout:rotate-layout:3.0.0' - implementation 'androidx.databinding:databinding-runtime:8.2.1' + implementation 'androidx.databinding:databinding-runtime:8.2.2' // implementation 'com.google.dagger:hilt-android:2.48' // kapt 'com.google.dagger:hilt-compiler:2.48' implementation "ch.acra:acra-mail:$acraVersion" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 56c8d27e..f8dbb569 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -48,4 +48,12 @@ ; } +-keepclassmembers class * extends com.google.protobuf.DescriptorProtos { + ; +} + +-keep class * extends com.google.protobuf.DescriptorProtos$**{ + *; +} + -keep,allowoptimization class com.aatorque.** { *; } diff --git a/app/src/main/java/com/aatorque/prefs/AlarmFragment.kt b/app/src/main/java/com/aatorque/prefs/AlarmFragment.kt index 70e02df4..12d22737 100644 --- a/app/src/main/java/com/aatorque/prefs/AlarmFragment.kt +++ b/app/src/main/java/com/aatorque/prefs/AlarmFragment.kt @@ -1,6 +1,5 @@ package com.aatorque.prefs -import com.aatorque.datastore.Operation import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -45,18 +44,17 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.fragment.app.Fragment -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import com.aatorque.datastore.Coloring import com.aatorque.datastore.Display +import com.aatorque.datastore.Operation import com.aatorque.datastore.Screen import com.aatorque.datastore.UserPreferenceOrBuilder import com.aatorque.stats.R @@ -64,7 +62,6 @@ import com.aatorque.ui.AppTheme import com.rarepebble.colorpicker.ColorPickerView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -414,7 +411,7 @@ fun VerticalReorderList(model: StateModel = viewModel()) { model.uiState.forEach { current -> ListItem( headlineContent = { - Text(current.operation.op) + Text("value ${current.operation.op} ${current.value}") }, leadingContent = { TextButton( @@ -434,11 +431,6 @@ fun VerticalReorderList(model: StateModel = viewModel()) { } }, - supportingContent = { - Text( - current.value.toString(), - ) - }, trailingContent = { Button(onClick = { diff --git a/app/src/main/java/com/aatorque/prefs/SettingsPIDFragment.kt b/app/src/main/java/com/aatorque/prefs/SettingsPIDFragment.kt index d6457d20..c6199e1d 100644 --- a/app/src/main/java/com/aatorque/prefs/SettingsPIDFragment.kt +++ b/app/src/main/java/com/aatorque/prefs/SettingsPIDFragment.kt @@ -22,10 +22,7 @@ import com.aatorque.stats.TorqueServiceWrapper import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch import timber.log.Timber import kotlin.math.ceil @@ -195,7 +192,9 @@ class SettingsPIDFragment: PreferenceFragmentCompat() { maxMarksActivePref.value = display.maxMarksActive.number.toString() highVisActivePref.isChecked = display.highVisActive colorPref.colorValue = display.chartColor.let { - if (it == 0) { + if (!isClock) { + Color.WHITE + } else if (it == 0) { resources.obtainTypedArray(R.array.chartColors).run { val color = getColor(index, Color.WHITE) recycle() diff --git a/app/src/main/java/com/aatorque/stats/TorqueData.kt b/app/src/main/java/com/aatorque/stats/TorqueData.kt index 17c970a7..c0779fd3 100644 --- a/app/src/main/java/com/aatorque/stats/TorqueData.kt +++ b/app/src/main/java/com/aatorque/stats/TorqueData.kt @@ -1,6 +1,5 @@ package com.aatorque.stats -import android.database.Observable import android.icu.math.BigDecimal import android.icu.text.NumberFormat import androidx.lifecycle.MutableLiveData @@ -63,7 +62,10 @@ class TorqueData(var display: Display) { } private fun updateAlarm(field: Double) { - currentAlarm.value = display.alarmsList.firstOrNull { + if (!hasReceivedNonZero) { + return + } + val newValue = display.alarmsList.lastOrNull { when (it.operation) { Operation.GT -> field > it.value Operation.GTE -> field >= it.value @@ -73,6 +75,9 @@ class TorqueData(var display: Display) { else -> false } } + if ((currentAlarm.value != null && newValue == null) || newValue?.equals(currentAlarm) == false) { + currentAlarm.postValue(newValue) + } } diff --git a/app/src/main/java/com/aatorque/stats/TorqueDisplay.kt b/app/src/main/java/com/aatorque/stats/TorqueDisplay.kt index e9dee768..28d9de0f 100644 --- a/app/src/main/java/com/aatorque/stats/TorqueDisplay.kt +++ b/app/src/main/java/com/aatorque/stats/TorqueDisplay.kt @@ -11,10 +11,13 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModelProvider +import com.aatorque.datastore.Coloring import com.aatorque.prefs.SettingsViewModel import com.aatorque.stats.databinding.FragmentDisplayBinding +import com.aatorque.utils.AwareObserver import timber.log.Timber + class TorqueDisplay : Fragment() { lateinit var rootView: View private var unit = "" @@ -82,14 +85,7 @@ class TorqueDisplay : Fragment() { binding.value = "-" } - -/* - if (icon == "empty") { - label.setBackgroundResource(0) - val params = label.layoutParams as ConstraintLayout.LayoutParams - params.width = 40 - label.layoutParams = params - } */ + alarmObserver.bind(viewLifecycleOwner, data.currentAlarm) } fun setupTypeface(typeface: Typeface) { @@ -100,4 +96,10 @@ class TorqueDisplay : Fragment() { fun onUpdate(data: TorqueData) { binding.value = data.lastDataStr + unit } + + val alarmObserver = object : AwareObserver() { + override fun onChanged(value: Coloring?) { + binding.alarm = value?.color + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/aatorque/stats/TorqueGauge.kt b/app/src/main/java/com/aatorque/stats/TorqueGauge.kt index 61f31f4c..c975a009 100644 --- a/app/src/main/java/com/aatorque/stats/TorqueGauge.kt +++ b/app/src/main/java/com/aatorque/stats/TorqueGauge.kt @@ -18,7 +18,6 @@ import com.aatorque.datastore.MaxControl import com.aatorque.prefs.SettingsViewModel import com.aatorque.stats.databinding.FragmentGaugeBinding import com.aatorque.utils.AwareObserver -import com.github.anastr.speedviewlib.ImageSpeedometer import com.github.anastr.speedviewlib.RaySpeedometer import com.github.anastr.speedviewlib.Speedometer import com.github.anastr.speedviewlib.components.Section @@ -32,7 +31,7 @@ val MIN_MAX_DEFAULT = Pair(0f, 100f) class TorqueGauge : Fragment() { private var rootView: View? = null - private val mClock: ImageSpeedometer + private val mClock: TorqueSpeedometer get() { return binding.dial } @@ -294,7 +293,7 @@ class TorqueGauge : Fragment() { val alarmObserver = object: AwareObserver() { override fun onChanged(value: Coloring?) { - binding.alarm = value + mClock.setAlarm(value) } } } \ No newline at end of file diff --git a/app/src/main/java/com/aatorque/stats/TorqueSpeedometer.kt b/app/src/main/java/com/aatorque/stats/TorqueSpeedometer.kt index f02d38f4..51b28493 100644 --- a/app/src/main/java/com/aatorque/stats/TorqueSpeedometer.kt +++ b/app/src/main/java/com/aatorque/stats/TorqueSpeedometer.kt @@ -1,20 +1,16 @@ package com.aatorque.stats -import android.animation.Animator -import android.animation.ValueAnimator import android.content.Context import android.graphics.Canvas -import android.graphics.ColorSpace import android.graphics.Paint -import android.graphics.Rect import android.graphics.RectF import android.graphics.drawable.Drawable -import android.support.v4.media.session.PlaybackStateCompat.RepeatMode import android.util.AttributeSet import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.core.graphics.toRect import com.aatorque.datastore.Coloring +import com.aatorque.utils.OpenCloseAnimator import com.github.anastr.speedviewlib.ImageSpeedometer class TorqueSpeedometer @JvmOverloads constructor( @@ -25,25 +21,19 @@ class TorqueSpeedometer @JvmOverloads constructor( var icon: Drawable? = null var alarmPaint: Paint? = null - val alarmBottomOffset = dpTOpx(5f) - val alarmAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration( - 300 - ).apply { - repeatCount = 0 + val alarmBottomOffset = dpTOpx(3f) + val alarmAnimator = OpenCloseAnimator.ofFloat(0f, 1f).apply { addUpdateListener { invalidate() } + duration = 300 } - var hasAlarmValue = false fun setAlarm(coloring: Coloring?) { - val value = coloring?.color - if (!hasAlarmValue && value != null) { - alarmAnimator.repeatMode = ValueAnimator.RESTART - alarmAnimator.pause() - alarmAnimator.start() + alarmAnimator.setState(coloring != null) + coloring?.let { alarmPaint = Paint().apply { - color = value ?: Color(0f, 0f, 0f, 0f).toArgb() + color = it.color style = Paint.Style.STROKE strokeWidth = alarmBottomOffset setShadowLayer( @@ -53,12 +43,7 @@ class TorqueSpeedometer @JvmOverloads constructor( Color(0f, 0f, 0f, 0.3f).toArgb() ) } - } else if (hasAlarmValue && value == null) { - alarmAnimator.repeatMode = ValueAnimator.REVERSE - alarmAnimator.pause() - alarmAnimator.start() } - hasAlarmValue = value != null } override fun onDraw(canvas: Canvas) { @@ -71,7 +56,12 @@ class TorqueSpeedometer @JvmOverloads constructor( start += start - (start * percent) distance *= percent canvas.drawArc( - RectF(0f, 0f, size.toFloat(), size.toFloat() - alarmBottomOffset), + RectF( + 0f + alarmBottomOffset, + 0f + alarmBottomOffset, + size.toFloat() - alarmBottomOffset, + size.toFloat() - alarmBottomOffset + ), start, distance, false, diff --git a/app/src/main/java/com/aatorque/stats/ViewAdapter.kt b/app/src/main/java/com/aatorque/stats/ViewAdapter.kt index 786d6643..8c28821c 100644 --- a/app/src/main/java/com/aatorque/stats/ViewAdapter.kt +++ b/app/src/main/java/com/aatorque/stats/ViewAdapter.kt @@ -7,8 +7,10 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.MarginLayoutParams import android.widget.ImageView +import android.widget.LinearLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.Guideline +import androidx.core.view.children import androidx.databinding.BindingAdapter import com.github.anastr.speedviewlib.Gauge import com.github.anastr.speedviewlib.ImageSpeedometer @@ -110,4 +112,16 @@ fun bitmapOrResource(view: ImageView, bitmap: Bitmap?, resource: Int?) { } else { view.setImageResource(0) } +} + +@BindingAdapter("reversed") +fun reversed(view: LinearLayout, isReversed: Boolean) { + if ((view.getTag(R.id.container) ?: false) != isReversed) { + val children = view.children.toList() + view.removeAllViews() + children.reversed().forEachIndexed { index, child -> + view.addView(child, index) + } + } + view.setTag(R.id.container, isReversed) } \ No newline at end of file diff --git a/app/src/main/java/com/aatorque/utils/AnimatedLine.kt b/app/src/main/java/com/aatorque/utils/AnimatedLine.kt new file mode 100644 index 00000000..276ece38 --- /dev/null +++ b/app/src/main/java/com/aatorque/utils/AnimatedLine.kt @@ -0,0 +1,74 @@ +package com.aatorque.utils + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb + + +class AnimatedLine(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) { + + var lastRect: RectF? = null + val animator = OpenCloseAnimator.ofFloat(0f, 1f).apply { + duration = 300 + } + + var paint: Paint? = null + var color: Int? = null + set(value) { + field = value + animator.setState(field != null) + field?.let { + paint = Paint().apply { + color = it + style = Paint.Style.FILL + strokeWidth = 3f + setShadowLayer( + 3f, + 0f, + 0f, + Color(0f, 0f, 0f, 0.5f).toArgb() + ) + } + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + lastRect?.let { lr -> + paint?.let { + canvas.drawRect( + lr, it + ) + } + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) + handler.post { + val midpoint = measuredWidth * 0.5f + animator.addUpdateListener { + lastRect = if (it.animatedValue == 0f) { + null + } else { + val halfWidthDiff = midpoint * it.animatedValue as Float + RectF( + midpoint - halfWidthDiff, + 0f, + midpoint + halfWidthDiff, + height.toFloat() + ) + } + invalidate() + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/aatorque/utils/OpenCloseAnimator.kt b/app/src/main/java/com/aatorque/utils/OpenCloseAnimator.kt new file mode 100644 index 00000000..04d00b45 --- /dev/null +++ b/app/src/main/java/com/aatorque/utils/OpenCloseAnimator.kt @@ -0,0 +1,42 @@ +package com.aatorque.utils + +import android.animation.ValueAnimator +import android.view.animation.DecelerateInterpolator + +class OpenCloseAnimator : ValueAnimator() { + + init { + repeatCount = 0 + interpolator = DecelerateInterpolator() + } + + var isOpen = false + var hasReversed = false + fun setState(hasValue: Boolean) { + if (hasValue) { + if (!isOpen) { + if (hasReversed && isRunning) { + reverse() + } else { + start() + } + hasReversed = false + isOpen = true + } + isOpen = true + } else if (isOpen) { + reverse() + isOpen = false + hasReversed = true + } + } + + companion object { + fun ofFloat(vararg values: Float): OpenCloseAnimator { + val anim = OpenCloseAnimator() + anim.setFloatValues(*values) + return anim + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index 1671a373..a1f9ee2a 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -298,7 +298,7 @@ android:layout_height="0dp" android:layout="@layout/fragment_display" android:clipChildren="false" - android:translationY="@{minMaxBelow ? -50 : 0}" + android:translationY="@{minMaxBelow ? -75 : 0}" app:layout_constraintRight_toRightOf="@{showChart ? @id/display2 : @id/line50}" app:layout_constraintLeft_toLeftOf="@{showChart ? @id/display2 : @id/line25}" app:layout_constraintTop_toBottomOf="@{showChart ? @id/display2 : LayoutParams.UNSET}" @@ -311,7 +311,7 @@ android:layout_height="0dp" android:layout="@layout/fragment_display" android:clipChildren="false" - android:translationY="@{minMaxBelow ? -50 : 0}" + android:translationY="@{minMaxBelow ? -75 : 0}" app:layout_constraintBottom_toBottomOf="@{showChart ? @id/chartFrag : @id/horizBottom}" app:layout_constraintRight_toRightOf="@{showChart ? @id/display3 : @id/line75}" app:layout_constraintLeft_toLeftOf="@{showChart ? @id/display3 : @id/line50}"/> diff --git a/app/src/main/res/layout/fragment_display.xml b/app/src/main/res/layout/fragment_display.xml index fd6d44ed..cc7289da 100644 --- a/app/src/main/res/layout/fragment_display.xml +++ b/app/src/main/res/layout/fragment_display.xml @@ -1,43 +1,52 @@ - + + + + - - + android:clipChildren="false" + android:gravity="center_horizontal" + android:orientation="vertical" + app:reversed="@{showBottom || showSide}"> + + + + + app:backgroundResource="@{icon ?? 0}" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_gauge.xml b/app/src/main/res/layout/fragment_gauge.xml index 41620b97..37722a46 100644 --- a/app/src/main/res/layout/fragment_gauge.xml +++ b/app/src/main/res/layout/fragment_gauge.xml @@ -7,7 +7,6 @@ - @@ -78,10 +77,6 @@ - - @@ -164,8 +159,7 @@ app:tickNumber="@{ticksOn ? TorqueGaugeKt.NUM_TICKS : 0}" app:onPrintTickLabel="@{ tickFormatter }" app:wholeNumbers="@{wholeNumbers}" - app:icon="@{icon}" - app:alarm="@{alarm}" /> + app:icon="@{icon}" /> Alarms "Change color based off the value to implement things like shift lights or temperature warnings. " Add item - Items are evaluated from top to bottom. The first item that matches will be used. + The last item that matches will be used. Condition Value Color