sforkowany z mirror/meshtastic-android
remove compose completely and stub out temp broken things
rodzic
8709d9db8c
commit
012139cb01
|
|
@ -29,7 +29,7 @@ android {
|
|||
|
||||
buildFeatures {
|
||||
// Enables Jetpack Compose for this module
|
||||
compose true // NOTE, if true main app crashes if you use regular view layout functions
|
||||
// compose true // NOTE, if true main app crashes if you use regular view layout functions
|
||||
}
|
||||
|
||||
// Set both the Java and Kotlin compilers to target Java 8.
|
||||
|
|
@ -44,8 +44,8 @@ android {
|
|||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerVersion "1.3.61-dev-withExperimentalGoogleExtensions-20200129"
|
||||
kotlinCompilerExtensionVersion "$compose_version"
|
||||
//kotlinCompilerVersion "1.3.61-dev-withExperimentalGoogleExtensions-20200129"
|
||||
//kotlinCompilerExtensionVersion "$compose_version"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,17 +99,6 @@ dependencies {
|
|||
// mapbox
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.0.0'
|
||||
|
||||
// You also need to include the following Compose toolkit dependencies.
|
||||
implementation("androidx.compose:compose-runtime:$compose_version")
|
||||
implementation("androidx.ui:ui-graphics:$compose_version")
|
||||
implementation("androidx.ui:ui-layout:$compose_version")
|
||||
implementation("androidx.ui:ui-material:$compose_version")
|
||||
implementation("androidx.ui:ui-unit:$compose_version")
|
||||
implementation("androidx.ui:ui-util:$compose_version")
|
||||
implementation "androidx.ui:ui-tooling:$compose_version"
|
||||
androidTestImplementation("androidx.ui:ui-platform:$compose_version")
|
||||
androidTestImplementation("androidx.ui:ui-test:$compose_version")
|
||||
|
||||
// location services
|
||||
implementation 'com.google.android.gms:play-services-location:17.0.0'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Copyright 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package androidx.ui.fakeandroidview
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.compose.Composable
|
||||
|
||||
/**
|
||||
* Composes an Android [View] given a layout resource [resId]. The method handles the inflation
|
||||
* of the [View] and will call the [postInflationCallback] after this happens. Note that the
|
||||
* callback will always be invoked on the main thread.
|
||||
*
|
||||
* @param resId The id of the layout resource to be inflated.
|
||||
* @param postInflationCallback The callback to be invoked after the layout is inflated.
|
||||
*/
|
||||
@Composable
|
||||
// TODO(popam): support modifiers here
|
||||
fun AndroidView(@LayoutRes resId: Int, postInflationCallback: (View) -> Unit = { _ -> }) {
|
||||
AndroidViewHolder(postInflationCallback = postInflationCallback, resId = resId)
|
||||
}
|
||||
|
||||
|
||||
private class AndroidViewHolder(context: Context) : ViewGroup(context) {
|
||||
var view: View? = null
|
||||
set(value) {
|
||||
if (value != field) {
|
||||
field = value
|
||||
removeAllViews()
|
||||
addView(view)
|
||||
}
|
||||
}
|
||||
|
||||
var postInflationCallback: (View) -> Unit = {}
|
||||
|
||||
var resId: Int? = null
|
||||
set(value) {
|
||||
if (value != field) {
|
||||
field = value
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val view = inflater.inflate(resId!!, this, false)
|
||||
this.view = view
|
||||
postInflationCallback(view)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
isClickable = true
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
view?.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
setMeasuredDimension(view?.measuredWidth ?: 0, view?.measuredHeight ?: 0)
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
view?.layout(l, t, r, b)
|
||||
}
|
||||
|
||||
override fun getLayoutParams(): LayoutParams? {
|
||||
return view?.layoutParams ?: LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement this method to handle touch screen motion events.
|
||||
*
|
||||
*
|
||||
* If this method is used to detect click actions, it is recommended that
|
||||
* the actions be performed by implementing and calling
|
||||
* [.performClick]. This will ensure consistent system behavior,
|
||||
* including:
|
||||
*
|
||||
* * obeying click sound preferences
|
||||
* * dispatching OnClickListener calls
|
||||
* * handling [ACTION_CLICK][AccessibilityNodeInfo.ACTION_CLICK] when
|
||||
* accessibility features are enabled
|
||||
*
|
||||
*
|
||||
* @param event The motion event.
|
||||
* @return True if the event was handled, false otherwise.
|
||||
*/
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,11 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
|||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.ServiceClient
|
||||
import com.geeksville.mesh.model.*
|
||||
import com.geeksville.mesh.model.TextMessage
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.*
|
||||
import com.geeksville.mesh.ui.*
|
||||
import com.geeksville.mesh.ui.ChannelFragment
|
||||
import com.geeksville.mesh.ui.MapFragment
|
||||
import com.geeksville.util.Exceptions
|
||||
import com.geeksville.util.exceptionReporter
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
|
|
@ -110,6 +112,17 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
// private val tabIndexes = generateSequence(0) { it + 1 } FIXME, instead do withIndex or zip? to get the ids below, also stop duplicating strings
|
||||
private val tabInfos = arrayOf(
|
||||
TabInfo(
|
||||
"Channel",
|
||||
R.drawable.ic_twotone_contactless_24,
|
||||
ChannelFragment()
|
||||
),
|
||||
TabInfo(
|
||||
"Map",
|
||||
R.drawable.ic_twotone_map_24,
|
||||
MapFragment()
|
||||
)
|
||||
/*
|
||||
TabInfo(
|
||||
"Messages",
|
||||
R.drawable.ic_twotone_message_24,
|
||||
|
|
@ -119,20 +132,10 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
"Users",
|
||||
R.drawable.ic_twotone_people_24,
|
||||
ComposeFragment("Users", 3) { UsersContent() }),
|
||||
TabInfo(
|
||||
"Channel",
|
||||
R.drawable.ic_twotone_contactless_24,
|
||||
ChannelFragment()
|
||||
),
|
||||
TabInfo(
|
||||
"Map",
|
||||
R.drawable.ic_twotone_map_24,
|
||||
MapFragment()
|
||||
),
|
||||
TabInfo(
|
||||
"Settings",
|
||||
R.drawable.ic_twotone_settings_applications_24,
|
||||
BTScanFragment("Settings", 2) { SettingsContent() })
|
||||
BTScanFragment("Settings", 2) { SettingsContent() }) */
|
||||
)
|
||||
|
||||
private
|
||||
|
|
@ -211,7 +214,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
private fun sendTestPackets() {
|
||||
exceptionReporter {
|
||||
val m = UIState.meshService!!
|
||||
val m = model.meshService!!
|
||||
|
||||
// Do some test operations
|
||||
val testPayload = "hello world".toByteArray()
|
||||
|
|
@ -232,10 +235,8 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val prefs = UIState.getPreferences(this)
|
||||
UIState.ownerName = prefs.getString("owner", "")!!
|
||||
UIState.meshService = null
|
||||
UIState.savedInstanceState = savedInstanceState
|
||||
val prefs = UIViewModel.getPreferences(this)
|
||||
model.ownerName.value = prefs.getString("owner", "")!!
|
||||
|
||||
// Ensures Bluetooth is available on the device and it is enabled. If not,
|
||||
// displays a dialog requesting user permission to enable Bluetooth.
|
||||
|
|
@ -301,19 +302,15 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
val appLinkAction = intent.action
|
||||
val appLinkData: Uri? = intent.data
|
||||
|
||||
UIState.requestedChannelUrl = null // assume none
|
||||
|
||||
// Were we asked to open one our channel URLs?
|
||||
if (Intent.ACTION_VIEW == appLinkAction) {
|
||||
debug("Asked to open a channel URL - FIXME, ask user if they want to switch to that channel. If so send the config to the radio")
|
||||
UIState.requestedChannelUrl = appLinkData
|
||||
val requestedChannelUrl = appLinkData
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterMeshReceiver()
|
||||
UIState.meshService =
|
||||
null // When our activity goes away make sure we don't keep a ptr around to the service
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
|
@ -371,40 +368,27 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
}
|
||||
|
||||
/// Read the config bytes from the radio so we can show them in our GUI, the radio's copy is ground truth
|
||||
private fun readRadioConfig() {
|
||||
val bytes = UIState.meshService!!.radioConfig
|
||||
|
||||
val config = MeshProtos.RadioConfig.parseFrom(bytes)
|
||||
UIState.setRadioConfig(this, config)
|
||||
|
||||
debug("Read config from radio")
|
||||
}
|
||||
|
||||
/// Called when we gain/lose a connection to our mesh radio
|
||||
private fun onMeshConnectionChanged(connected: MeshService.ConnectionState) {
|
||||
UIState.isConnected.value = connected
|
||||
debug("connchange ${UIState.isConnected.value}")
|
||||
model.isConnected.value = connected
|
||||
debug("connchange ${model.isConnected.value}")
|
||||
if (connected == MeshService.ConnectionState.CONNECTED) {
|
||||
// always get the current radio config when we connect
|
||||
readRadioConfig()
|
||||
|
||||
// everytime the radio reconnects, we slam in our current owner data, the radio is smart enough to only broadcast if needed
|
||||
UIState.setOwner(this)
|
||||
model.setOwner(this)
|
||||
|
||||
val m = UIState.meshService!!
|
||||
val m = model.meshService!!
|
||||
|
||||
// Pull down our real node ID
|
||||
NodeDB.myId.value = m.myId
|
||||
model.nodeDB.myId.value = m.myId
|
||||
|
||||
// Update our nodeinfos based on data from the device
|
||||
NodeDB.nodes.clear()
|
||||
NodeDB.nodes.putAll(
|
||||
m.nodes.map
|
||||
{
|
||||
it.user?.id!! to it
|
||||
}
|
||||
)
|
||||
val nodes = m.nodes.map {
|
||||
it.user?.id!! to it
|
||||
}.toMap()
|
||||
|
||||
model.nodeDB.nodes.value = nodes
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -435,7 +419,8 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
|
||||
// We only care about nodes that have user info
|
||||
info.user?.id?.let {
|
||||
NodeDB.nodes[it] = info
|
||||
val newnodes = model.nodeDB.nodes.value!! + Pair(it, info)
|
||||
model.nodeDB.nodes.value = newnodes
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -456,7 +441,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
payload.toString(utf8)
|
||||
)
|
||||
|
||||
MessagesState.addMessage(msg)
|
||||
model.messagesState.addMessage(msg)
|
||||
}
|
||||
else -> TODO()
|
||||
}
|
||||
|
|
@ -482,7 +467,6 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
com.geeksville.mesh.IMeshService.Stub.asInterface(it)
|
||||
}) {
|
||||
override fun onConnected(service: com.geeksville.mesh.IMeshService) {
|
||||
UIState.meshService = service
|
||||
model.meshService = service
|
||||
|
||||
// We don't start listening for packets until after we are connected to the service
|
||||
|
|
@ -493,19 +477,19 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
MeshService.ConnectionState.valueOf(service.connectionState())
|
||||
onMeshConnectionChanged(connectionState)
|
||||
|
||||
debug("connected to mesh service, isConnected=${UIState.isConnected.value}")
|
||||
debug("connected to mesh service, isConnected=${model.isConnected.value}")
|
||||
}
|
||||
|
||||
override fun onDisconnected() {
|
||||
unregisterMeshReceiver()
|
||||
UIState.meshService = null
|
||||
model.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
|
||||
if (UIState.meshService != null)
|
||||
if (model.meshService != null)
|
||||
Exceptions.reportError("meshService was supposed to be null, ignoring (but reporting a bug)")
|
||||
|
||||
MeshService.startService(this)?.let { intent ->
|
||||
|
|
@ -520,7 +504,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
// if we never connected, do nothing
|
||||
debug("Unbinding from mesh service!")
|
||||
mesh.close()
|
||||
UIState.meshService = null
|
||||
model.meshService = null
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.geeksville.mesh.model
|
|||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import androidx.compose.Model
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
|
|
@ -11,7 +10,6 @@ import com.journeyapps.barcodescanner.BarcodeEncoder
|
|||
import java.net.MalformedURLException
|
||||
|
||||
|
||||
@Model
|
||||
data class Channel(
|
||||
var name: String,
|
||||
var modemConfig: MeshProtos.ChannelSettings.ModemConfig,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.geeksville.mesh.model
|
||||
|
||||
import android.os.RemoteException
|
||||
import androidx.compose.frames.modelListOf
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
|
|
@ -21,8 +21,8 @@ data class TextMessage(
|
|||
)
|
||||
|
||||
|
||||
object MessagesState : Logging {
|
||||
private val testTexts = arrayOf(
|
||||
class MessagesState(private val ui: UIViewModel) : Logging {
|
||||
private val testTexts = listOf(
|
||||
TextMessage(
|
||||
"+16508765310",
|
||||
"I found the cache"
|
||||
|
|
@ -35,17 +35,20 @@ object MessagesState : Logging {
|
|||
|
||||
// If the following (unused otherwise) line is commented out, the IDE preview window works.
|
||||
// if left in the preview always renders as empty.
|
||||
val messages = modelListOf(* if (isEmulator) testTexts else arrayOf())
|
||||
val messages =
|
||||
object : MutableLiveData<List<TextMessage>>(if (isEmulator) testTexts else listOf()) {
|
||||
|
||||
}
|
||||
|
||||
/// add a message our GUI list of past msgs
|
||||
fun addMessage(m: TextMessage) {
|
||||
messages.add(m)
|
||||
messages.value = messages.value!! + m
|
||||
}
|
||||
|
||||
/// Send a message and added it to our GUI log
|
||||
fun sendMessage(str: String, dest: String? = null) {
|
||||
var error: String? = null
|
||||
val service = UIState.meshService
|
||||
val service = ui.meshService
|
||||
if (service != null)
|
||||
try {
|
||||
service.sendData(
|
||||
|
|
@ -59,9 +62,9 @@ object MessagesState : Logging {
|
|||
else
|
||||
error = "Error: No Mesh service"
|
||||
|
||||
MessagesState.addMessage(
|
||||
addMessage(
|
||||
TextMessage(
|
||||
NodeDB.myId.value,
|
||||
ui.nodeDB.myId.value!!,
|
||||
str,
|
||||
errorMessage = error
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
package com.geeksville.mesh.model
|
||||
|
||||
import androidx.compose.frames.modelMapOf
|
||||
import androidx.compose.mutableStateOf
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.mesh.MeshUser
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.Position
|
||||
|
||||
object NodeDB {
|
||||
|
||||
/// NodeDB lives inside the UIViewModel, but it needs a backpointer to reach the service
|
||||
class NodeDB(private val ui: UIViewModel) {
|
||||
private val testPositions = arrayOf(
|
||||
Position(32.776665, -96.796989, 35), // dallas
|
||||
Position(32.960758, -96.733521, 35), // richardson
|
||||
|
|
@ -43,12 +44,14 @@ object NodeDB {
|
|||
private val seedWithTestNodes = isEmulator
|
||||
|
||||
/// The unique ID of our node
|
||||
val myId = mutableStateOf(if (isEmulator) "+16508765309" else "invalid")
|
||||
val myId = object : MutableLiveData<String>(if (isEmulator) "+16508765309" else "invalid") {}
|
||||
|
||||
/// A map from nodeid to to nodeinfo
|
||||
val nodes =
|
||||
modelMapOf(* (if (isEmulator) testNodes else listOf()).map { it.user!!.id to it }.toTypedArray())
|
||||
object :
|
||||
MutableLiveData<Map<String, NodeInfo>>(mapOf(*(if (isEmulator) testNodes else listOf()).map { it.user!!.id to it }
|
||||
.toTypedArray())) {}
|
||||
|
||||
/// Could be null if we haven't received our node DB yet
|
||||
val ourNodeInfo get() = nodes[myId.value]
|
||||
val ourNodeInfo get() = nodes.value!![myId.value]
|
||||
}
|
||||
|
|
@ -3,11 +3,8 @@ package com.geeksville.mesh.model
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.RemoteException
|
||||
import androidx.compose.mutableStateOf
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
|
|
@ -15,7 +12,15 @@ import com.geeksville.android.Logging
|
|||
import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.ui.getInitials
|
||||
|
||||
/// Given a human name, strip out the first letter of the first three words and return that as the initials for
|
||||
/// that user.
|
||||
fun getInitials(name: String): String {
|
||||
val words = name.split(Regex("\\s+")).filter { it.isNotEmpty() }.take(3).map { it.first() }
|
||||
.joinToString("")
|
||||
|
||||
return words
|
||||
}
|
||||
|
||||
class UIViewModel : ViewModel(), Logging {
|
||||
init {
|
||||
|
|
@ -41,32 +46,15 @@ class UIViewModel : ViewModel(), Logging {
|
|||
context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
debug("ViewModel cleared")
|
||||
}
|
||||
|
||||
var meshService: IMeshService? = null
|
||||
|
||||
val nodeDB = NodeDB(this)
|
||||
val messagesState = MessagesState(this)
|
||||
|
||||
/// Are we connected to our radio device
|
||||
val isConnected =
|
||||
object : LiveData<MeshService.ConnectionState>(MeshService.ConnectionState.DISCONNECTED) {
|
||||
/**
|
||||
* Called when the number of active observers change to 1 from 0.
|
||||
*
|
||||
*
|
||||
* This callback can be used to know that this LiveData is being used thus should be kept
|
||||
* up to date.
|
||||
*/
|
||||
override fun onActive() {
|
||||
super.onActive()
|
||||
|
||||
// Get the current radio config from the service
|
||||
meshService?.let {
|
||||
debug("Getting connection state from service")
|
||||
value = MeshService.ConnectionState.valueOf(it.connectionState())
|
||||
}
|
||||
}
|
||||
object :
|
||||
MutableLiveData<MeshService.ConnectionState>(MeshService.ConnectionState.DISCONNECTED) {
|
||||
}
|
||||
|
||||
/// various radio settings (including the channel)
|
||||
|
|
@ -89,6 +77,11 @@ class UIViewModel : ViewModel(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
debug("ViewModel cleared")
|
||||
}
|
||||
|
||||
/// Set the radio config (also updates our saved copy in preferences)
|
||||
fun setRadioConfig(context: Context, c: MeshProtos.RadioConfig) {
|
||||
debug("Setting new radio config!")
|
||||
|
|
@ -96,66 +89,28 @@ class UIViewModel : ViewModel(), Logging {
|
|||
radioConfig.value = c
|
||||
|
||||
getPreferences(context).edit(commit = true) {
|
||||
this.putString("channel-url", UIState.getChannel()!!.getChannelUrl().toString())
|
||||
this.putString("channel-url", getChannel(c)!!.getChannelUrl().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
||||
object UIState : Logging {
|
||||
|
||||
/// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME
|
||||
// lateinit var googleSignInClient: GoogleSignInClient
|
||||
|
||||
var meshService: IMeshService? = null
|
||||
|
||||
/// Are we connected to our radio device
|
||||
val isConnected = mutableStateOf(MeshService.ConnectionState.DISCONNECTED)
|
||||
|
||||
/// various radio settings (including the channel)
|
||||
private val radioConfig = mutableStateOf<MeshProtos.RadioConfig?>(null)
|
||||
|
||||
/// our name in hte radio
|
||||
/// Note, we generate owner initials automatically for now
|
||||
/// our activity will read this from prefs or set it to the empty string
|
||||
var ownerName: String = "MrInIDE Ownername"
|
||||
val ownerName = object : MutableLiveData<String>("MrIDE Test") {
|
||||
}
|
||||
|
||||
|
||||
/// If the app was launched because we received a new channel intent, the Url will be here
|
||||
var requestedChannelUrl: Uri? = null
|
||||
|
||||
var savedInstanceState: Bundle? = null
|
||||
|
||||
/**
|
||||
* Return the current channel info
|
||||
* FIXME, we should sim channels at the MeshService level if we are running on an emulator,
|
||||
* for now I just fake it by returning a canned channel.
|
||||
*/
|
||||
fun getChannel(): Channel? {
|
||||
val channel = radioConfig.value?.channelSettings?.let { Channel(it) }
|
||||
|
||||
return if (channel == null && isEmulator)
|
||||
Channel.emulated
|
||||
else
|
||||
channel
|
||||
}
|
||||
|
||||
fun getPreferences(context: Context): SharedPreferences =
|
||||
context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
|
||||
|
||||
/// Set the radio config (also updates our saved copy in preferences)
|
||||
fun setRadioConfig(context: Context, c: MeshProtos.RadioConfig) {
|
||||
radioConfig.value = c
|
||||
|
||||
getPreferences(context).edit(commit = true) {
|
||||
this.putString("channel-url", getChannel()!!.getChannelUrl().toString())
|
||||
}
|
||||
}
|
||||
|
||||
// clean up all this nasty owner state management FIXME
|
||||
fun setOwner(context: Context, s: String? = null) {
|
||||
|
||||
if (s != null) {
|
||||
ownerName = s
|
||||
ownerName.value = s
|
||||
|
||||
// note: we allow an empty userstring to be written to prefs
|
||||
getPreferences(context).edit(commit = true) {
|
||||
|
|
@ -164,15 +119,16 @@ object UIState : Logging {
|
|||
}
|
||||
|
||||
// Note: we are careful to not set a new unique ID
|
||||
if (ownerName.isNotEmpty())
|
||||
if (ownerName.value!!.isNotEmpty())
|
||||
try {
|
||||
meshService?.setOwner(
|
||||
null,
|
||||
ownerName,
|
||||
getInitials(ownerName)
|
||||
ownerName.value,
|
||||
getInitials(ownerName.value!!)
|
||||
) // Note: we use ?. here because we might be running in the emulator
|
||||
} catch (ex: RemoteException) {
|
||||
errormsg("Can't set username on device, is device offline? ${ex.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.Composable
|
||||
import androidx.ui.core.DensityAmbient
|
||||
import androidx.ui.core.DrawModifier
|
||||
import androidx.ui.core.Modifier
|
||||
import androidx.ui.core.asModifier
|
||||
import androidx.ui.foundation.Box
|
||||
import androidx.ui.graphics.*
|
||||
import androidx.ui.graphics.colorspace.ColorSpaces
|
||||
import androidx.ui.graphics.painter.ImagePainter
|
||||
import androidx.ui.unit.Density
|
||||
import androidx.ui.unit.PxSize
|
||||
import androidx.ui.unit.toRect
|
||||
|
||||
/// Stolen from the Compose SimpleImage, replace with their real Image component someday
|
||||
// TODO(mount, malkov) : remove when RepaintBoundary is a modifier: b/149982905
|
||||
// This is class and not val because if b/149985596
|
||||
private object ClipModifier : DrawModifier {
|
||||
override fun draw(density: Density, drawContent: () -> Unit, canvas: Canvas, size: PxSize) {
|
||||
canvas.save()
|
||||
canvas.clipRect(size.toRect())
|
||||
drawContent()
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Stolen from the Compose SimpleImage, replace with their real Image component someday
|
||||
@Composable
|
||||
fun ScaledImage(
|
||||
image: ImageAsset,
|
||||
modifier: Modifier = Modifier.None,
|
||||
tint: Color? = null
|
||||
) {
|
||||
with(DensityAmbient.current) {
|
||||
val imageModifier = ImagePainter(image).asModifier(
|
||||
scaleFit = ScaleFit.FillMaxDimension,
|
||||
colorFilter = tint?.let { ColorFilter(it, BlendMode.srcIn) }
|
||||
)
|
||||
Box(modifier + ClipModifier + imageModifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Borrowed from Compose
|
||||
class AndroidImage(val bitmap: Bitmap) : ImageAsset {
|
||||
|
||||
/**
|
||||
* @see Image.width
|
||||
*/
|
||||
override val width: Int
|
||||
get() = bitmap.width
|
||||
|
||||
/**
|
||||
* @see Image.height
|
||||
*/
|
||||
override val height: Int
|
||||
get() = bitmap.height
|
||||
|
||||
override val config: ImageAssetConfig get() = ImageAssetConfig.Argb8888
|
||||
|
||||
/**
|
||||
* @see Image.colorSpace
|
||||
*/
|
||||
override val colorSpace: androidx.ui.graphics.colorspace.ColorSpace
|
||||
get() = ColorSpaces.Srgb
|
||||
|
||||
/**
|
||||
* @see Image.hasAlpha
|
||||
*/
|
||||
override val hasAlpha: Boolean
|
||||
get() = bitmap.hasAlpha()
|
||||
|
||||
/**
|
||||
* @see Image.nativeImage
|
||||
*/
|
||||
override val nativeImage: NativeImageAsset
|
||||
get() = bitmap
|
||||
|
||||
/**
|
||||
* @see
|
||||
*/
|
||||
override fun prepareToDraw() {
|
||||
bitmap.prepareToDraw()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +1,7 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.le.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.ParcelUuid
|
||||
import androidx.compose.Composable
|
||||
import androidx.compose.Model
|
||||
import androidx.compose.frames.modelMapOf
|
||||
import androidx.compose.onCommit
|
||||
import androidx.ui.core.ContextAmbient
|
||||
import androidx.ui.foundation.Text
|
||||
import androidx.ui.layout.Column
|
||||
import androidx.ui.layout.LayoutGravity
|
||||
import androidx.ui.material.CircularProgressIndicator
|
||||
import androidx.ui.material.MaterialTheme
|
||||
import androidx.ui.material.ProvideEmphasis
|
||||
import androidx.ui.material.RadioGroup
|
||||
import androidx.ui.tooling.preview.Preview
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.service.RadioInterfaceService
|
||||
import com.geeksville.util.exceptionReporter
|
||||
|
||||
|
||||
/*
|
||||
@Model
|
||||
object ScanUIState {
|
||||
var selectedMacAddr: String? = null
|
||||
|
|
@ -171,14 +147,6 @@ fun BTScanScreen() {
|
|||
} else {
|
||||
// val allPaired = bluetoothAdapter?.bondedDevices.orEmpty().map { it.address }.toSet()
|
||||
|
||||
/* Only let user select paired devices
|
||||
val paired = devices.values.filter { allPaired.contains(it.macAddress) }
|
||||
if (paired.size < devices.size) {
|
||||
Text(
|
||||
"Warning: there are nearby Meshtastic devices that are not paired with this phone. Before you can select a device, you will need to pair it in Bluetooth Settings."
|
||||
)
|
||||
} */
|
||||
|
||||
RadioGroup {
|
||||
Column {
|
||||
ScanUIState.devices.values.forEach {
|
||||
|
|
@ -237,10 +205,4 @@ fun BTScanScreen() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun btScanScreenPreview() {
|
||||
BTScanScreen()
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,30 +1,16 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import androidx.compose.Composable
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.ui.core.ContextAmbient
|
||||
import androidx.ui.foundation.Text
|
||||
import androidx.ui.input.ImeAction
|
||||
import androidx.ui.layout.*
|
||||
import androidx.ui.material.MaterialTheme
|
||||
import androidx.ui.material.OutlinedButton
|
||||
import androidx.ui.tooling.preview.Preview
|
||||
import androidx.ui.unit.dp
|
||||
import com.geeksville.analytics.DataPair
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.model.toHumanString
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
|
||||
object ChannelLog : Logging
|
||||
|
|
@ -68,7 +54,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@Composable
|
||||
fun ChannelContent(channel: Channel?) {
|
||||
|
||||
|
|
@ -165,12 +151,4 @@ fun ChannelContent(channel: Channel?) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun previewChannel() {
|
||||
// another bug? It seems modaldrawerlayout not yet supported in preview
|
||||
MaterialTheme(colors = palette) {
|
||||
ChannelContent(Channel.emulated)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.Composable
|
||||
import androidx.ui.core.setContent
|
||||
|
||||
fun androidx.fragment.app.Fragment.setComposable(
|
||||
id: Int,
|
||||
content: @Composable() () -> Unit
|
||||
): View? =
|
||||
context?.let {
|
||||
FrameLayout(it).apply {
|
||||
this.isClickable = true
|
||||
this.id =
|
||||
id // Compose requires a unique ID for the containing view to make savedInstanceState work
|
||||
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
setContent(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class ComposeFragment(
|
||||
screenName: String,
|
||||
id: Int,
|
||||
private val content: @Composable() () -> Unit
|
||||
) : ScreenFragment(screenName) {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? =
|
||||
FrameLayout(context!!).apply {
|
||||
this.isClickable = true
|
||||
this.id =
|
||||
id // Compose requires a unique ID for the containing view to make savedInstanceState work
|
||||
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
setContent(content)
|
||||
}
|
||||
|
||||
/* override fun onStart() {
|
||||
super.onStart()
|
||||
(view as ViewGroup).setContent(content)
|
||||
} */
|
||||
}
|
||||
|
|
@ -5,10 +5,12 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.NodeInfo
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.NodeDB
|
||||
import com.geeksville.mesh.model.UIState
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.mapbox.geojson.Feature
|
||||
import com.mapbox.geojson.FeatureCollection
|
||||
import com.mapbox.geojson.Point
|
||||
|
|
@ -30,6 +32,8 @@ import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
|
|||
|
||||
class MapFragment : ScreenFragment("Map"), Logging {
|
||||
|
||||
private val model: UIViewModel by activityViewModels()
|
||||
|
||||
private val nodeSourceId = "node-positions"
|
||||
private val nodeLayerId = "node-layer"
|
||||
private val labelLayerId = "label-layer"
|
||||
|
|
@ -52,56 +56,61 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
textAllowOverlap(true)
|
||||
)
|
||||
|
||||
private fun nodesWithPosition() = NodeDB.nodes.values.filter { it.validPosition != null }
|
||||
|
||||
/**
|
||||
* Using the latest nodedb, generate geojson features
|
||||
*/
|
||||
private fun getCurrentNodes(): FeatureCollection {
|
||||
// Find all nodes with valid locations
|
||||
private fun onNodesChanged(map: MapboxMap, nodes: Collection<NodeInfo>) {
|
||||
val nodesWithPosition = nodes.filter { it.validPosition != null }
|
||||
|
||||
val locations = nodesWithPosition().map { node ->
|
||||
val p = node.position!!
|
||||
debug("Showing on map: $node")
|
||||
/**
|
||||
* Using the latest nodedb, generate geojson features
|
||||
*/
|
||||
fun getCurrentNodes(): FeatureCollection {
|
||||
// Find all nodes with valid locations
|
||||
|
||||
val f = Feature.fromGeometry(
|
||||
Point.fromLngLat(
|
||||
p.longitude,
|
||||
p.latitude
|
||||
val locations = nodesWithPosition.map { node ->
|
||||
val p = node.position!!
|
||||
debug("Showing on map: $node")
|
||||
|
||||
val f = Feature.fromGeometry(
|
||||
Point.fromLngLat(
|
||||
p.longitude,
|
||||
p.latitude
|
||||
)
|
||||
)
|
||||
)
|
||||
node.user?.let {
|
||||
f.addStringProperty("name", it.longName)
|
||||
node.user?.let {
|
||||
f.addStringProperty("name", it.longName)
|
||||
}
|
||||
f
|
||||
}
|
||||
f
|
||||
|
||||
return FeatureCollection.fromFeatures(locations)
|
||||
}
|
||||
|
||||
return FeatureCollection.fromFeatures(locations)
|
||||
}
|
||||
fun zoomToNodes(map: MapboxMap) {
|
||||
if (nodesWithPosition.isNotEmpty()) {
|
||||
val update = if (nodesWithPosition.size >= 2) {
|
||||
// Multiple nodes, make them all fit on the map view
|
||||
val bounds = LatLngBounds.Builder()
|
||||
|
||||
private fun zoomToNodes(map: MapboxMap) {
|
||||
val nodes = nodesWithPosition()
|
||||
if (nodes.isNotEmpty()) {
|
||||
val update = if (nodes.size >= 2) {
|
||||
// Multiple nodes, make them all fit on the map view
|
||||
val bounds = LatLngBounds.Builder()
|
||||
// Add all positions
|
||||
bounds.includes(nodes.map { it.position!! }
|
||||
.map { LatLng(it.latitude, it.longitude) })
|
||||
|
||||
// Add all positions
|
||||
bounds.includes(nodes.map { it.position!! }
|
||||
.map { LatLng(it.latitude, it.longitude) })
|
||||
CameraUpdateFactory.newLatLngBounds(bounds.build(), 150)
|
||||
} else {
|
||||
// Only one node, just zoom in on it
|
||||
val it = nodesWithPosition[0].position!!
|
||||
|
||||
CameraUpdateFactory.newLatLngBounds(bounds.build(), 150)
|
||||
} else {
|
||||
// Only one node, just zoom in on it
|
||||
val it = nodes[0].position!!
|
||||
|
||||
val cameraPos = CameraPosition.Builder().target(
|
||||
LatLng(it.latitude, it.longitude)
|
||||
).zoom(9.0).build()
|
||||
CameraUpdateFactory.newCameraPosition(cameraPos)
|
||||
val cameraPos = CameraPosition.Builder().target(
|
||||
LatLng(it.latitude, it.longitude)
|
||||
).zoom(9.0).build()
|
||||
CameraUpdateFactory.newCameraPosition(cameraPos)
|
||||
}
|
||||
map.animateCamera(update, 1000)
|
||||
}
|
||||
map.animateCamera(update, 1000)
|
||||
}
|
||||
|
||||
nodePositions.setGeoJson(getCurrentNodes()) // Update node positions
|
||||
zoomToNodes(map)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
@ -115,12 +124,12 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
mapView = view.findViewById(R.id.mapView)
|
||||
mapView.onCreate(UIState.savedInstanceState)
|
||||
mapView.onCreate(savedInstanceState)
|
||||
|
||||
mapView.getMapAsync { map ->
|
||||
|
||||
// val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24)
|
||||
val markerIcon = activity!!.getDrawable(R.drawable.ic_twotone_person_pin_24)!!
|
||||
val markerIcon = requireActivity().getDrawable(R.drawable.ic_twotone_person_pin_24)!!
|
||||
|
||||
map.setStyle(Style.OUTDOORS) { style ->
|
||||
style.addSource(nodePositions)
|
||||
|
|
@ -129,6 +138,10 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
style.addLayer(labelLayer)
|
||||
}
|
||||
|
||||
model.nodeDB.nodes.observe(viewLifecycleOwner, Observer { nodes ->
|
||||
onNodesChanged(map, nodes.values)
|
||||
})
|
||||
|
||||
//map.uiSettings.isScrollGesturesEnabled = true
|
||||
//map.uiSettings.isZoomGesturesEnabled = true
|
||||
}
|
||||
|
|
@ -152,11 +165,6 @@ class MapFragment : ScreenFragment("Map"), Logging {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
mapView.onResume()
|
||||
|
||||
mapView.getMapAsync { map ->
|
||||
nodePositions.setGeoJson(getCurrentNodes()) // Update node positions
|
||||
zoomToNodes(map)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.ui.material.lightColorPalette
|
||||
import com.geeksville.android.Logging
|
||||
|
||||
|
||||
object UILog : Logging
|
||||
|
||||
/*
|
||||
val palette = lightColorPalette() // darkColorPalette()
|
||||
|
||||
/*
|
||||
|
||||
@Composable
|
||||
fun MeshApp() {
|
||||
val (drawerState, onDrawerStateChange) = state { DrawerState.Closed }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
/*
|
||||
import androidx.compose.Composable
|
||||
import androidx.compose.state
|
||||
import androidx.ui.core.Modifier
|
||||
|
|
@ -31,9 +32,7 @@ val TimestampEmphasis = object : Emphasis {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* A pretty version the text, with user icon to the left, name and time of arrival (copy slack look and feel)
|
||||
*/
|
||||
/// A pretty version the text, with user icon to the left, name and time of arrival (copy slack look and feel)
|
||||
@Composable
|
||||
fun MessageCard(msg: TextMessage, modifier: Modifier = Modifier.None) {
|
||||
Row(modifier = modifier) {
|
||||
|
|
@ -112,11 +111,4 @@ fun MessagesContent() {
|
|||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun previewMessagesView() {
|
||||
// another bug? It seems modaldrawerlayout not yet supported in preview
|
||||
MaterialTheme(colors = palette) {
|
||||
MessagesContent()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
/*
|
||||
import androidx.compose.Composable
|
||||
import androidx.ui.foundation.Text
|
||||
import androidx.ui.layout.*
|
||||
|
|
@ -13,22 +14,7 @@ import com.geeksville.mesh.model.NodeDB
|
|||
import androidx.ui.core.Modifier as Modifier1
|
||||
|
||||
|
||||
/*
|
||||
@Composable
|
||||
fun NodeIcon(modifier: Modifier1 = Modifier1.None, node: NodeInfo) {
|
||||
Column {
|
||||
Container(modifier = modifier + LayoutSize(40.dp, 40.dp)) {
|
||||
VectorImage(id = if (node.user?.shortName != null) R.drawable.person else R.drawable.help)
|
||||
}
|
||||
|
||||
// Show our shortname if possible
|
||||
/* node.user?.shortName?.let {
|
||||
Text(it)
|
||||
} */
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun CompassHeading(modifier: Modifier1 = Modifier1.None, node: NodeInfo) {
|
||||
|
|
@ -81,11 +67,4 @@ fun NodeInfoCard(node: NodeInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun nodeInfoPreview() {
|
||||
Column {
|
||||
NodeInfoCard(NodeDB.testNodes[0])
|
||||
NodeInfoCard(NodeDB.testNodeNoPosition)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
/*
|
||||
import androidx.compose.Composable
|
||||
import androidx.compose.state
|
||||
import androidx.ui.core.ContextAmbient
|
||||
|
|
@ -68,12 +69,4 @@ fun SettingsContent() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun previewSettings() {
|
||||
// another bug? It seems modaldrawerlayout not yet supported in preview
|
||||
MaterialTheme(colors = palette) {
|
||||
SettingsContent()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
/*
|
||||
data class ScreenInfo(val icon: Int, val label: String)
|
||||
|
||||
// defines the screens we have in the app
|
||||
object Screen {
|
||||
val settings = ScreenInfo(R.drawable.ic_twotone_settings_applications_24, "Settings")
|
||||
val channel = ScreenInfo(R.drawable.ic_twotone_contactless_24, "Channel")
|
||||
val users = ScreenInfo(R.drawable.ic_twotone_people_24, "Users")
|
||||
val messages = ScreenInfo(R.drawable.ic_twotone_message_24, "Messages")
|
||||
val map = ScreenInfo(R.drawable.ic_twotone_map_24, "Map")
|
||||
}
|
||||
|
||||
|
||||
@Model
|
||||
object AppStatus {
|
||||
var currentScreen: ScreenInfo = Screen.messages
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Temporary solution pending navigation support.
|
||||
*/
|
||||
fun navigateTo(destination: ScreenInfo) {
|
||||
AppStatus.currentScreen = destination
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.compose.Composable
|
||||
import androidx.compose.state
|
||||
import androidx.ui.core.Modifier
|
||||
import androidx.ui.foundation.TextField
|
||||
import androidx.ui.foundation.shape.corner.RoundedCornerShape
|
||||
import androidx.ui.graphics.Color
|
||||
import androidx.ui.input.ImeAction
|
||||
import androidx.ui.input.KeyboardType
|
||||
import androidx.ui.input.VisualTransformation
|
||||
import androidx.ui.layout.LayoutPadding
|
||||
import androidx.ui.material.Emphasis
|
||||
import androidx.ui.material.MaterialTheme
|
||||
import androidx.ui.material.ProvideEmphasis
|
||||
import androidx.ui.material.Surface
|
||||
import androidx.ui.text.TextStyle
|
||||
import androidx.ui.unit.dp
|
||||
|
||||
|
||||
val HintEmphasis = object : Emphasis {
|
||||
override fun emphasize(color: Color) = color.copy(alpha = 0.05f)
|
||||
}
|
||||
|
||||
|
||||
/// A text field that visually conveys that it is editable - FIXME, once Compose has material
|
||||
/// design text fields use that instead.
|
||||
@Composable
|
||||
fun StyledTextField(
|
||||
value: String,
|
||||
modifier: Modifier = Modifier.None,
|
||||
onValueChange: (String) -> Unit = {},
|
||||
textStyle: TextStyle = TextStyle.Default,
|
||||
keyboardType: KeyboardType = KeyboardType.Text,
|
||||
imeAction: ImeAction = ImeAction.Unspecified,
|
||||
onFocus: () -> Unit = {},
|
||||
onBlur: () -> Unit = {},
|
||||
focusIdentifier: String? = null,
|
||||
onImeActionPerformed: (ImeAction) -> Unit = {},
|
||||
visualTransformation: VisualTransformation? = null,
|
||||
hintText: String = ""
|
||||
) {
|
||||
val backgroundColor = palette.secondary.copy(alpha = 0.12f)
|
||||
Surface(
|
||||
modifier = LayoutPadding(8.dp),
|
||||
color = backgroundColor,
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
) {
|
||||
val showingHint = state { value.isEmpty() }
|
||||
val level = if (showingHint.value) HintEmphasis else MaterialTheme.emphasisLevels.medium
|
||||
|
||||
ProvideEmphasis(level) {
|
||||
TextField(
|
||||
value.ifEmpty { if (showingHint.value) hintText else "" },
|
||||
modifier + LayoutPadding(4.dp),
|
||||
onValueChange,
|
||||
textStyle,
|
||||
keyboardType,
|
||||
imeAction,
|
||||
{
|
||||
showingHint.value = false // Stop showing the hint now
|
||||
onFocus()
|
||||
},
|
||||
{
|
||||
// if the string is empty again, return to the hint text
|
||||
showingHint.value = value.isEmpty()
|
||||
onBlur()
|
||||
},
|
||||
focusIdentifier,
|
||||
onImeActionPerformed,
|
||||
visualTransformation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
/*
|
||||
import androidx.compose.Composable
|
||||
import androidx.ui.core.Modifier
|
||||
import androidx.ui.foundation.Text
|
||||
|
|
@ -34,11 +35,4 @@ fun UserIcon(user: NodeInfo? = null, modifier: Modifier = Modifier.None) {
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun previewUserIcon() {
|
||||
// another bug? It seems modaldrawerlayout not yet supported in preview
|
||||
MaterialTheme(colors = palette) {
|
||||
UserIcon(NodeDB.testNodes[1])
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
/*
|
||||
import androidx.compose.Composable
|
||||
import androidx.ui.core.ContextAmbient
|
||||
import androidx.ui.foundation.Text
|
||||
|
|
@ -16,14 +17,7 @@ import com.geeksville.mesh.service.RadioInterfaceService
|
|||
import com.geeksville.mesh.service.SoftwareUpdateService
|
||||
|
||||
|
||||
/// Given a human name, strip out the first letter of the first three words and return that as the initials for
|
||||
/// that user.
|
||||
fun getInitials(name: String): String {
|
||||
val words = name.split(Regex("\\s+")).filter { it.isNotEmpty() }.take(3).map { it.first() }
|
||||
.joinToString("")
|
||||
|
||||
return words
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UsersContent() {
|
||||
|
|
@ -88,3 +82,5 @@ fun UsersContent() {
|
|||
} */
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.Composable
|
||||
import androidx.ui.core.Modifier
|
||||
import androidx.ui.foundation.Icon
|
||||
import androidx.ui.graphics.Color
|
||||
import androidx.ui.graphics.vector.drawVector
|
||||
import androidx.ui.layout.Container
|
||||
import androidx.ui.layout.LayoutSize
|
||||
import androidx.ui.material.IconButton
|
||||
import androidx.ui.res.vectorResource
|
||||
|
||||
|
||||
@Composable
|
||||
fun VectorImageButton(@DrawableRes id: Int, onClick: () -> Unit) {
|
||||
//Ripple(bounded = false) {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(vectorResource(id) /* , modifier = LayoutSize(40.dp, 40.dp) */)
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
/* fun AppBarIcon(icon: Image, onClick: () -> Unit) {
|
||||
Container(width = ActionIconDiameter, height = ActionIconDiameter) {
|
||||
Ripple(bounded = false) {
|
||||
Clickable(onClick = onClick) {
|
||||
SimpleImage(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
@Composable
|
||||
fun VectorImage(
|
||||
modifier: Modifier = Modifier.None, @DrawableRes id: Int,
|
||||
tint: Color = Color.Transparent
|
||||
) {
|
||||
val vector = vectorResource(id)
|
||||
// WithDensity {
|
||||
Container(
|
||||
modifier = modifier + LayoutSize(
|
||||
vector.defaultWidth,
|
||||
vector.defaultHeight
|
||||
) + drawVector(vector, tint)
|
||||
) {
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.61'
|
||||
ext.compose_version = '0.1.0-dev08'
|
||||
ext.coroutines_version = "1.3.5"
|
||||
|
||||
repositories {
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue