Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
dbfb08412d
feat: Reconnect button (#6) 2024-08-17 01:00:51 +02:00
eddcf47205
feat: UI basics (#6) 2024-08-17 00:14:53 +02:00
c7fc9e9c09
fix: Garmin connection method rebuilt 2024-08-16 23:08:43 +02:00
8 changed files with 212 additions and 24 deletions

View file

@ -17,6 +17,7 @@ import java.io.IOException
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.SynchronousQueue
import java.util.concurrent.atomic.AtomicBoolean
@SuppressLint("MissingPermission")
class Garmin(
@ -47,6 +48,14 @@ class Garmin(
locations.start()
}
fun reconnect() {
Log.d(TAG, "reconnect pressed")
if (!connection.isConnecting.get()) {
Log.d(TAG, "reconnection enqueued")
context.runAsync { connection.tryConnect() }
}
}
fun close() {
connection.close()
@ -184,7 +193,9 @@ class Garmin(
private inner class ConnectThread : Thread() {
val isConnecting = AtomicBoolean(false)
private val queue: SynchronousQueue<IntArray> = SynchronousQueue()
private val state = AtomicBoolean(false)
private var current: IntArray = intArrayOf()
private val socket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
@ -192,18 +203,37 @@ class Garmin(
device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
}
fun tryConnect() {
Log.d(TAG, "Connection requested")
if (!isConnecting.get()) {
Log.d(TAG, "Trying to connect...")
isConnecting.set(true)
context.checkBt()
// Cancel discovery because it otherwise slows down the connection.
adapter.cancelDiscovery()
try {
socket?.connect()
context.toastConnectionStatus(true)
sleep(3000)
readAll()
send(intArrayOf(0x07, 0x01)) // Set GPS to true
send(intArrayOf(0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) // Clear screen
state.set(true)
} catch (e: IOException) {
Log.d(TAG, "Not connected", e)
context.toastConnectionStatus(false)
state.set(false)
}
queue.clear()
current = intArrayOf()
isConnecting.set(false)
}
}
override fun run() {
// Cancel discovery because it otherwise slows down the connection.
context.checkBt()
adapter.cancelDiscovery()
try {
socket?.connect()
context.setConnectionStatus(true)
sleep(3000)
readAll()
send(intArrayOf(0x07, 0x01)) // Set GPS to true
send(intArrayOf(0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)) // Clear screen
while (true) {
tryConnect()
while (true) {
if (state.get()) {
val newCurrent = queue.poll()
if (newCurrent == null) {
Log.d(TAG, "run (${currentThread().name}): Sleep...")
@ -212,15 +242,9 @@ class Garmin(
current = newCurrent
}
send(current)
}
} catch (e: IOException) {
Log.d(TAG, "Not connected", e)
context.setConnectionStatus(false)
while (true) {
// Just dequeue
// TODO: Add option to reconnect
queue.poll()
sleep(900)
} else {
queue.clear()
sleep(2000)
}
}
}
@ -253,9 +277,13 @@ class Garmin(
}
private fun sendRaw(buff: IntArray) {
buff.forEach { socket!!.outputStream.write(it) }
socket!!.outputStream.flush()
readAll()
try {
buff.forEach { socket!!.outputStream.write(it) }
socket!!.outputStream.flush()
readAll()
} catch (e: Exception) {
Log.d(TAG, "sendRaw: ${e.message}")
}
}
}

View file

@ -17,6 +17,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.lifecycleScope
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
@ -25,6 +26,9 @@ import com.mapbox.navigation.core.lifecycle.requireMapboxNavigation
import eu.ztsh.garmin.databinding.ActivityMainBinding
import eu.ztsh.garmin.mapbox.MapControl
import eu.ztsh.garmin.util.PermissionsHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.ref.WeakReference
@ -124,7 +128,15 @@ class MainActivity : AppCompatActivity() {
return true
}
fun setConnectionStatus(success: Boolean) {
fun runAsync(action: () -> Unit) {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
action()
}
}
}
fun toastConnectionStatus(success: Boolean) {
this.runOnUiThread {
if (success) {
Toast.makeText(this, "Garmin connected", Toast.LENGTH_LONG).show()

View file

@ -59,4 +59,6 @@ class UI(b: ActivityMainBinding) {
val searchResultsView = b.searchResults
val searchPlaceView = b.searchPlaces
val queryEditText = b.query
val reconnect = b.reconnect
}

View file

@ -22,6 +22,7 @@ import com.mapbox.navigation.ui.maps.camera.data.MapboxNavigationViewportDataSou
import com.mapbox.navigation.ui.maps.camera.lifecycle.NavigationBasicGesturesHandler
import com.mapbox.navigation.ui.maps.camera.state.NavigationCameraState
import com.mapbox.navigation.ui.maps.location.NavigationLocationProvider
import eu.ztsh.garmin.Garmin
import eu.ztsh.garmin.MainActivity
import eu.ztsh.garmin.UI
import eu.ztsh.garmin.mock.ReplayResources
@ -105,6 +106,12 @@ class MapControl(
locationObserver = LocationObserver(this)
routeProgressObserver = routeControl.routeProgressObserver
voiceInstructionsObserver = voiceControl.voiceInstructionsObserver
ui.reconnect.setOnClickListener {
Garmin.instance.reconnect()
ui.reconnect.showTextAndExtend(UI.BUTTON_ANIMATION_DURATION)
}
}
fun routeToPoint(point: Point) {

View file

@ -0,0 +1,119 @@
package eu.ztsh.garmin.view
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.annotation.StyleRes
import androidx.annotation.UiThread
import androidx.constraintlayout.widget.ConstraintLayout
import com.mapbox.navigation.ui.components.R
import eu.ztsh.garmin.R.styleable.*
import com.mapbox.navigation.ui.components.databinding.ExtendableButtonLayoutBinding
import com.mapbox.navigation.ui.utils.internal.ExtendableButtonHelper
/**
* Mapbox extendable generic.
*/
@UiThread
class ExtendableButton : ConstraintLayout {
private val binding = ExtendableButtonLayoutBinding.inflate(LayoutInflater.from(context), this)
private val helper = ExtendableButtonHelper(
binding.buttonText,
context.resources.getDimensionPixelSize(R.dimen.mapbox_button_size),
context.resources.getDimension(R.dimen.mapbox_recenterButton_minExtendedWidth),
)
private lateinit var expandedText: String
/**
*
* @param context Context
* @constructor
*/
constructor(context: Context) : super(context)
/**
*
* @param context Context
* @param attrs AttributeSet?
* @constructor
*/
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initAttributes(attrs)
}
/**
*
* @param context Context
* @param attrs AttributeSet?
* @param defStyleAttr Int
* @constructor
*/
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int
) : super(context, attrs, defStyleAttr) {
initAttributes(attrs)
}
/**
* Allows you to change the style of [ExtendableButton].
* @param style Int
*/
fun updateStyle(@StyleRes style: Int) {
val typedArray = context.obtainStyledAttributes(
style,
ExtendableButton
)
applyAttributes(typedArray)
typedArray.recycle()
}
/**
* Invoke the function to show optional text associated with the view.
* @param duration for the view to be in the extended mode before it starts to shrink.
* @param text for the view to show in the extended mode.
*/
@JvmOverloads
fun showTextAndExtend(
duration: Long,
text: String = expandedText,
) {
if (!helper.isAnimationRunning) {
helper.showTextAndExtend(text, duration)
}
}
private fun initAttributes(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(
attrs,
ExtendableButton,
0,
0
)
applyAttributes(typedArray)
typedArray.recycle()
}
private fun applyAttributes(typedArray: TypedArray) {
typedArray.getDrawable(ExtendableButton_btn_icon)
.also { binding.buttonIcon.setImageDrawable(it) }
typedArray.getDrawable(
R.styleable.MapboxRecenterButton_recenterButtonBackground,
)?.let { background ->
binding.buttonIcon.background = background
binding.buttonText.background = background
}
typedArray.getColorStateList(
R.styleable.MapboxRecenterButton_recenterButtonTextColor,
)?.let { binding.buttonText.setTextColor(it) }
typedArray.apply {
expandedText = getString(ExtendableButton_expanded_text) ?: "DUNNO"
}
}
}

View file

@ -127,4 +127,15 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/routeOverview" />
<eu.ztsh.garmin.view.ExtendableButton
android:id="@+id/reconnect"
app:btn_icon="@android:drawable/ic_menu_rotate"
app:expanded_text="@string/reconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/recenter" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExtendableButton">
<attr name="expanded_text" format="string"/>
<attr name="btn_icon" format="reference"/>
</declare-styleable>
</resources>

View file

@ -7,4 +7,6 @@
<string name="place_autocomplete_selection_error">Error happened during request</string>
<string name="not_implemented_yet">Not implemented yet</string>
<string name="reconnect">Reconnect</string>
</resources>