kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
test playstore upload
rodzic
9dcfb59ee0
commit
2401b3d0b3
|
@ -12,3 +12,4 @@
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
|
/app/release
|
8
TODO.md
8
TODO.md
|
@ -1,8 +1,7 @@
|
||||||
# High priority
|
# High priority
|
||||||
MVP features required for first public alpha
|
MVP features required for first public alpha
|
||||||
|
|
||||||
* use google signin to get user name (later give other options)
|
* let user set name and shortname
|
||||||
* always set a _unique_ owner id (if changed)
|
|
||||||
* stop scan when we start the service
|
* stop scan when we start the service
|
||||||
* set the radio by using the service
|
* set the radio by using the service
|
||||||
* startforegroundservice only if we have a valid radio
|
* startforegroundservice only if we have a valid radio
|
||||||
|
@ -52,6 +51,8 @@ Do this "Signal app compatible" release relatively soon after the alpha release
|
||||||
# Medium priority
|
# Medium priority
|
||||||
Things for the betaish period.
|
Things for the betaish period.
|
||||||
|
|
||||||
|
* use google signin to get user name
|
||||||
|
* use Firebase Test Lab
|
||||||
* let user pick/specify a name through ways other than google signin (for the privacy concerned, or devices without Play API)
|
* let user pick/specify a name through ways other than google signin (for the privacy concerned, or devices without Play API)
|
||||||
* make my android app show mesh state
|
* make my android app show mesh state
|
||||||
* show qr code for each channel https://medium.com/@aanandshekharroy/generate-barcode-in-android-app-using-zxing-64c076a5d83a
|
* show qr code for each channel https://medium.com/@aanandshekharroy/generate-barcode-in-android-app-using-zxing-64c076a5d83a
|
||||||
|
@ -113,4 +114,5 @@ Don't leave device discoverable. Don't let unpaired users do things with device
|
||||||
* use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state
|
* use https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#4 to show service state
|
||||||
* all chat in the app defaults to group chat
|
* all chat in the app defaults to group chat
|
||||||
* start bt receive on boot
|
* start bt receive on boot
|
||||||
* warn user to bt pair
|
* warn user to bt pair
|
||||||
|
* suppress logging output if running a release build (required for play store)
|
|
@ -14,10 +14,10 @@ android {
|
||||||
buildToolsVersion "29.0.2"
|
buildToolsVersion "29.0.2"
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.geeksville.mesh"
|
applicationId "com.geeksville.mesh"
|
||||||
minSdkVersion 21
|
minSdkVersion 22 // The oldest emulator image I have tried is 22 (though 21 probably works)
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0"
|
versionName "0.1"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -99,6 +99,9 @@ dependencies {
|
||||||
androidTestImplementation("androidx.ui:ui-platform:$compose_version")
|
androidTestImplementation("androidx.ui:ui-platform:$compose_version")
|
||||||
androidTestImplementation("androidx.ui:ui-test:$compose_version")
|
androidTestImplementation("androidx.ui:ui-test:$compose_version")
|
||||||
|
|
||||||
|
// For Google Sign-In (owner name accesss)
|
||||||
|
implementation 'com.google.android.gms:play-services-auth:17.0.0'
|
||||||
|
|
||||||
// Add the Firebase SDK for Crashlytics.
|
// Add the Firebase SDK for Crashlytics.
|
||||||
implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta01'
|
implementation 'com.google.firebase:firebase-crashlytics:17.0.0-beta01'
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,11 @@ import com.geeksville.mesh.ui.MeshApp
|
||||||
import com.geeksville.mesh.ui.TextMessage
|
import com.geeksville.mesh.ui.TextMessage
|
||||||
import com.geeksville.mesh.ui.UIState
|
import com.geeksville.mesh.ui.UIState
|
||||||
import com.geeksville.util.exceptionReporter
|
import com.geeksville.util.exceptionReporter
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||||
|
import com.google.android.gms.common.api.ApiException
|
||||||
|
import com.google.android.gms.tasks.Task
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -30,12 +35,12 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
companion object {
|
companion object {
|
||||||
const val REQUEST_ENABLE_BT = 10
|
const val REQUEST_ENABLE_BT = 10
|
||||||
const val DID_REQUEST_PERM = 11
|
const val DID_REQUEST_PERM = 11
|
||||||
|
const val RC_SIGN_IN = 12 // google signin completed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val utf8 = Charset.forName("UTF-8")
|
private val utf8 = Charset.forName("UTF-8")
|
||||||
|
|
||||||
|
|
||||||
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
|
private val bluetoothAdapter: BluetoothAdapter? by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
bluetoothManager.adapter
|
bluetoothManager.adapter
|
||||||
|
@ -93,214 +98,253 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
|
|
||||||
|
|
||||||
private fun setOwner() {
|
private fun setOwner() {
|
||||||
try {
|
// Note: we are careful to not set a new unique ID
|
||||||
|
meshService!!.setOwner(null, "Kevin Xter", "kx")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendTestPackets() {
|
||||||
|
exceptionReporter {
|
||||||
|
val m = meshService!!
|
||||||
|
|
||||||
// Note: we are careful to not set a new unique ID
|
// Do some test operations
|
||||||
meshService!!.setOwner(null, "Kevin Xter", "kx")
|
val testPayload = "hello world".toByteArray()
|
||||||
}
|
m.sendData(
|
||||||
|
"+16508675310",
|
||||||
private fun sendTestPackets() {
|
testPayload,
|
||||||
exceptionReporter {
|
MeshProtos.Data.Type.SIGNAL_OPAQUE_VALUE
|
||||||
val m = meshService!!
|
)
|
||||||
|
m.sendData(
|
||||||
// Do some test operations
|
"+16508675310",
|
||||||
val testPayload = "hello world".toByteArray()
|
testPayload,
|
||||||
m.sendData(
|
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
|
||||||
"+16508675310",
|
)
|
||||||
testPayload,
|
|
||||||
MeshProtos.Data.Type.SIGNAL_OPAQUE_VALUE
|
|
||||||
)
|
|
||||||
m.sendData(
|
|
||||||
"+16508675310",
|
|
||||||
testPayload,
|
|
||||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
setContent {
|
|
||||||
MeshApp()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensures Bluetooth is available on the device and it is enabled. If not,
|
|
||||||
// displays a dialog requesting user permission to enable Bluetooth.
|
|
||||||
if (bluetoothAdapter != null) {
|
|
||||||
bluetoothAdapter!!.takeIf { !it.isEnabled }?.apply {
|
|
||||||
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
|
||||||
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, "Error - this app requires bluetooth", Toast.LENGTH_LONG)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
requestPermission()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
unregisterMeshReceiver()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var receiverRegistered = false
|
|
||||||
|
|
||||||
private fun registerMeshReceiver() {
|
|
||||||
logAssert(!receiverRegistered)
|
|
||||||
val filter = IntentFilter()
|
|
||||||
filter.addAction(MeshService.ACTION_MESH_CONNECTED)
|
|
||||||
filter.addAction(MeshService.ACTION_NODE_CHANGE)
|
|
||||||
filter.addAction(MeshService.ACTION_RECEIVED_DATA)
|
|
||||||
registerReceiver(meshServiceReceiver, filter)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unregisterMeshReceiver() {
|
|
||||||
if (receiverRegistered) {
|
|
||||||
receiverRegistered = false
|
|
||||||
unregisterReceiver(meshServiceReceiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when we gain/lose a connection to our mesh radio
|
|
||||||
private fun onMeshConnectionChanged(connected: Boolean) {
|
|
||||||
UIState.isConnected.value = connected
|
|
||||||
debug("connchange ${UIState.isConnected.value}")
|
|
||||||
if (connected) {
|
|
||||||
// everytime the radio reconnects, we slam in our current owner data
|
|
||||||
setOwner()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val meshServiceReceiver = object : BroadcastReceiver() {
|
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
|
|
||||||
debug("Received from mesh service $intent")
|
|
||||||
|
|
||||||
when (intent.action) {
|
|
||||||
MeshService.ACTION_NODE_CHANGE -> {
|
|
||||||
val info: NodeInfo = intent.getParcelableExtra(EXTRA_NODEINFO)!!
|
|
||||||
debug("UI nodechange $info")
|
|
||||||
|
|
||||||
// We only care about nodes that have user info
|
|
||||||
info.user?.id?.let {
|
|
||||||
val newnodes = UIState.nodes.value.toMutableMap()
|
|
||||||
newnodes[it] = info
|
|
||||||
UIState.nodes.value = newnodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MeshService.ACTION_RECEIVED_DATA -> {
|
|
||||||
debug("TODO rxdata")
|
|
||||||
val sender = intent.getStringExtra(EXTRA_SENDER)!!
|
|
||||||
val payload = intent.getByteArrayExtra(EXTRA_PAYLOAD)!!
|
|
||||||
val typ = intent.getIntExtra(EXTRA_TYP, -1)
|
|
||||||
|
|
||||||
when (typ) {
|
|
||||||
MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> {
|
|
||||||
// FIXME - use the real time from the packet
|
|
||||||
val modded = UIState.messages.value.toMutableList()
|
|
||||||
modded.add(TextMessage(Date(), sender, payload.toString(utf8)))
|
|
||||||
UIState.messages.value = modded
|
|
||||||
}
|
|
||||||
else -> TODO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MeshService.ACTION_MESH_CONNECTED -> {
|
|
||||||
val connected = intent.getBooleanExtra(EXTRA_CONNECTED, false)
|
|
||||||
onMeshConnectionChanged(connected)
|
|
||||||
}
|
|
||||||
else -> TODO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var meshService: IMeshService? = null
|
|
||||||
private var isBound = false
|
|
||||||
|
|
||||||
private var serviceConnection = object : ServiceConnection {
|
|
||||||
override fun onServiceConnected(name: ComponentName, service: IBinder) =
|
|
||||||
exceptionReporter {
|
|
||||||
val m = IMeshService.Stub.asInterface(service)
|
|
||||||
meshService = m
|
|
||||||
|
|
||||||
// We don't start listening for packets until after we are connected to the service
|
|
||||||
registerMeshReceiver()
|
|
||||||
|
|
||||||
// We won't receive a notify for the initial state of connection, so we force an update here
|
|
||||||
onMeshConnectionChanged(m.isConnected)
|
|
||||||
|
|
||||||
debug("connected to mesh service, isConnected=${UIState.isConnected.value}")
|
|
||||||
|
|
||||||
// make some placeholder nodeinfos
|
|
||||||
UIState.nodes.value =
|
|
||||||
m.nodes.toList().map {
|
|
||||||
it.user?.id!! to it
|
|
||||||
}.toMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName) {
|
|
||||||
warn("The mesh service has disconnected")
|
|
||||||
unregisterMeshReceiver()
|
|
||||||
meshService = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindMeshService() {
|
|
||||||
debug("Binding to mesh service!")
|
|
||||||
// we bind using the well known name, to make sure 3rd party apps could also
|
|
||||||
logAssert(meshService == null)
|
|
||||||
|
|
||||||
val intent = MeshService.startService(this)
|
|
||||||
if (intent != null) {
|
|
||||||
// ALSO bind so we can use the api
|
|
||||||
logAssert(bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE))
|
|
||||||
isBound = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unbindMeshService() {
|
|
||||||
// If we have received the service, and hence registered with
|
|
||||||
// it, then now is the time to unregister.
|
|
||||||
// if we never connected, do nothing
|
|
||||||
debug("Unbinding from mesh service!")
|
|
||||||
if (isBound)
|
|
||||||
unbindService(serviceConnection)
|
|
||||||
meshService = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
unregisterMeshReceiver() // No point in receiving updates while the GUI is gone, we'll get them when the user launches the activity
|
|
||||||
unbindMeshService()
|
|
||||||
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
bindMeshService()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
|
||||||
menuInflater.inflate(R.menu.menu_main, menu)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
// Handle action bar item clicks here. The action bar will
|
|
||||||
// automatically handle clicks on the Home/Up button, so long
|
|
||||||
// as you specify a parent activity in AndroidManifest.xml.
|
|
||||||
return when (item.itemId) {
|
|
||||||
R.id.action_settings -> true
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
MeshApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensures Bluetooth is available on the device and it is enabled. If not,
|
||||||
|
// displays a dialog requesting user permission to enable Bluetooth.
|
||||||
|
if (bluetoothAdapter != null) {
|
||||||
|
bluetoothAdapter!!.takeIf { !it.isEnabled }?.apply {
|
||||||
|
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
|
||||||
|
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Error - this app requires bluetooth", Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
requestPermission()
|
||||||
|
|
||||||
|
// Configure sign-in to request the user's ID, email address, and basic
|
||||||
|
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
|
||||||
|
// Configure sign-in to request the user's ID, email address, and basic
|
||||||
|
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
|
||||||
|
val gso =
|
||||||
|
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||||
|
.requestEmail()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Build a GoogleSignInClient with the options specified by gso.
|
||||||
|
UIState.googleSignInClient = GoogleSignIn.getClient(this, gso);
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
unregisterMeshReceiver()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch incoming result to the correct fragment.
|
||||||
|
*/
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
|
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
|
||||||
|
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
|
||||||
|
if (requestCode === RC_SIGN_IN) {
|
||||||
|
// The Task returned from this call is always completed, no need to attach
|
||||||
|
// a listener.
|
||||||
|
val task: Task<GoogleSignInAccount> =
|
||||||
|
GoogleSignIn.getSignedInAccountFromIntent(data)
|
||||||
|
handleSignInResult(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
|
||||||
|
try {
|
||||||
|
val account =
|
||||||
|
completedTask.getResult(ApiException::class.java)
|
||||||
|
// Signed in successfully, show authenticated UI.
|
||||||
|
//updateUI(account)
|
||||||
|
} catch (e: ApiException) { // The ApiException status code indicates the detailed failure reason.
|
||||||
|
// Please refer to the GoogleSignInStatusCodes class reference for more information.
|
||||||
|
warn("signInResult:failed code=" + e.statusCode)
|
||||||
|
//updateUI(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var receiverRegistered = false
|
||||||
|
|
||||||
|
private fun registerMeshReceiver() {
|
||||||
|
logAssert(!receiverRegistered)
|
||||||
|
val filter = IntentFilter()
|
||||||
|
filter.addAction(MeshService.ACTION_MESH_CONNECTED)
|
||||||
|
filter.addAction(MeshService.ACTION_NODE_CHANGE)
|
||||||
|
filter.addAction(MeshService.ACTION_RECEIVED_DATA)
|
||||||
|
registerReceiver(meshServiceReceiver, filter)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unregisterMeshReceiver() {
|
||||||
|
if (receiverRegistered) {
|
||||||
|
receiverRegistered = false
|
||||||
|
unregisterReceiver(meshServiceReceiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when we gain/lose a connection to our mesh radio
|
||||||
|
private fun onMeshConnectionChanged(connected: Boolean) {
|
||||||
|
UIState.isConnected.value = connected
|
||||||
|
debug("connchange ${UIState.isConnected.value}")
|
||||||
|
if (connected) {
|
||||||
|
// everytime the radio reconnects, we slam in our current owner data
|
||||||
|
setOwner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val meshServiceReceiver = object : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
|
||||||
|
debug("Received from mesh service $intent")
|
||||||
|
|
||||||
|
when (intent.action) {
|
||||||
|
MeshService.ACTION_NODE_CHANGE -> {
|
||||||
|
val info: NodeInfo = intent.getParcelableExtra(EXTRA_NODEINFO)!!
|
||||||
|
debug("UI nodechange $info")
|
||||||
|
|
||||||
|
// We only care about nodes that have user info
|
||||||
|
info.user?.id?.let {
|
||||||
|
val newnodes = UIState.nodes.value.toMutableMap()
|
||||||
|
newnodes[it] = info
|
||||||
|
UIState.nodes.value = newnodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshService.ACTION_RECEIVED_DATA -> {
|
||||||
|
debug("TODO rxdata")
|
||||||
|
val sender = intent.getStringExtra(EXTRA_SENDER)!!
|
||||||
|
val payload = intent.getByteArrayExtra(EXTRA_PAYLOAD)!!
|
||||||
|
val typ = intent.getIntExtra(EXTRA_TYP, -1)
|
||||||
|
|
||||||
|
when (typ) {
|
||||||
|
MeshProtos.Data.Type.CLEAR_TEXT_VALUE -> {
|
||||||
|
// FIXME - use the real time from the packet
|
||||||
|
val modded = UIState.messages.value.toMutableList()
|
||||||
|
modded.add(TextMessage(Date(), sender, payload.toString(utf8)))
|
||||||
|
UIState.messages.value = modded
|
||||||
|
}
|
||||||
|
else -> TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MeshService.ACTION_MESH_CONNECTED -> {
|
||||||
|
val connected = intent.getBooleanExtra(EXTRA_CONNECTED, false)
|
||||||
|
onMeshConnectionChanged(connected)
|
||||||
|
}
|
||||||
|
else -> TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var meshService: IMeshService? = null
|
||||||
|
private var isBound = false
|
||||||
|
|
||||||
|
private var serviceConnection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(name: ComponentName, service: IBinder) =
|
||||||
|
exceptionReporter {
|
||||||
|
val m = IMeshService.Stub.asInterface(service)
|
||||||
|
meshService = m
|
||||||
|
|
||||||
|
// We don't start listening for packets until after we are connected to the service
|
||||||
|
registerMeshReceiver()
|
||||||
|
|
||||||
|
// We won't receive a notify for the initial state of connection, so we force an update here
|
||||||
|
onMeshConnectionChanged(m.isConnected)
|
||||||
|
|
||||||
|
debug("connected to mesh service, isConnected=${UIState.isConnected.value}")
|
||||||
|
|
||||||
|
// make some placeholder nodeinfos
|
||||||
|
UIState.nodes.value =
|
||||||
|
m.nodes.toList().map {
|
||||||
|
it.user?.id!! to it
|
||||||
|
}.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
warn("The mesh service has disconnected")
|
||||||
|
unregisterMeshReceiver()
|
||||||
|
meshService = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindMeshService() {
|
||||||
|
debug("Binding to mesh service!")
|
||||||
|
// we bind using the well known name, to make sure 3rd party apps could also
|
||||||
|
logAssert(meshService == null)
|
||||||
|
|
||||||
|
val intent = MeshService.startService(this)
|
||||||
|
if (intent != null) {
|
||||||
|
// ALSO bind so we can use the api
|
||||||
|
logAssert(bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE))
|
||||||
|
isBound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unbindMeshService() {
|
||||||
|
// If we have received the service, and hence registered with
|
||||||
|
// it, then now is the time to unregister.
|
||||||
|
// if we never connected, do nothing
|
||||||
|
debug("Unbinding from mesh service!")
|
||||||
|
if (isBound)
|
||||||
|
unbindService(serviceConnection)
|
||||||
|
meshService = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
unregisterMeshReceiver() // No point in receiving updates while the GUI is gone, we'll get them when the user launches the activity
|
||||||
|
unbindMeshService()
|
||||||
|
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
bindMeshService()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
menuInflater.inflate(R.menu.menu_main, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
// Handle action bar item clicks here. The action bar will
|
||||||
|
// automatically handle clicks on the Home/Up button, so long
|
||||||
|
// as you specify a parent activity in AndroidManifest.xml.
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.action_settings -> true
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.geeksville.mesh
|
||||||
|
|
||||||
import android.os.Debug
|
import android.os.Debug
|
||||||
import com.geeksville.android.GeeksvilleApplication
|
import com.geeksville.android.GeeksvilleApplication
|
||||||
|
import com.geeksville.android.Logging
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +11,8 @@ class MeshUtilApplication : GeeksvilleApplication(null, "58e72ccc361883ea502510b
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
Logging.showLogs = BuildConfig.DEBUG
|
||||||
|
|
||||||
// We default to off in the manifest, FIXME turn on only if user approves
|
// We default to off in the manifest, FIXME turn on only if user approves
|
||||||
// leave off when running in the debugger
|
// leave off when running in the debugger
|
||||||
if (false && !Debug.isDebuggerConnected())
|
if (false && !Debug.isDebuggerConnected())
|
||||||
|
|
|
@ -36,6 +36,22 @@ fun HomeContent() {
|
||||||
Text("Text: ${it.text}")
|
Text("Text: ${it.text}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FIXME - doens't work yet - probably because I'm not using release keys
|
||||||
|
// If account is null, then show the signin button, otherwise
|
||||||
|
val context = ambient(ContextAmbient)
|
||||||
|
val account = GoogleSignIn.getLastSignedInAccount(context)
|
||||||
|
if (account != null)
|
||||||
|
Text("We have an account")
|
||||||
|
else {
|
||||||
|
Text("No account yet")
|
||||||
|
if (context is Activity) {
|
||||||
|
Button("Google sign-in", onClick = {
|
||||||
|
val signInIntent: Intent = UIState.googleSignInClient.signInIntent
|
||||||
|
context.startActivityForResult(signInIntent, MainActivity.RC_SIGN_IN)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Button(text = "Start scan",
|
Button(text = "Start scan",
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.compose.mutableStateOf
|
||||||
import com.geeksville.mesh.MeshUser
|
import com.geeksville.mesh.MeshUser
|
||||||
import com.geeksville.mesh.NodeInfo
|
import com.geeksville.mesh.NodeInfo
|
||||||
import com.geeksville.mesh.Position
|
import com.geeksville.mesh.Position
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
// defines the screens we have in the app
|
// defines the screens we have in the app
|
||||||
|
@ -22,7 +23,10 @@ data class TextMessage(val date: Date, val from: String, val text: String)
|
||||||
|
|
||||||
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
||||||
object UIState {
|
object UIState {
|
||||||
|
|
||||||
|
/// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME
|
||||||
|
lateinit var googleSignInClient: GoogleSignInClient
|
||||||
|
|
||||||
private val testPositions = arrayOf(
|
private val testPositions = arrayOf(
|
||||||
Position(32.776665, -96.796989, 35), // dallas
|
Position(32.776665, -96.796989, 35), // dallas
|
||||||
Position(32.960758, -96.733521, 35), // richardson
|
Position(32.960758, -96.733521, 35), // richardson
|
||||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 156 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 69 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 67 KiB |
Ładowanie…
Reference in New Issue