remove compose completely and stub out temp broken things

1.2-legacy
geeksville 2020-04-08 09:53:04 -07:00
rodzic 8709d9db8c
commit 012139cb01
22 zmienionych plików z 161 dodań i 728 usunięć

Wyświetl plik

@ -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'

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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,

Wyświetl plik

@ -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
)

Wyświetl plik

@ -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]
}

Wyświetl plik

@ -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}")
}
}
}

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -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()
}
*/

Wyświetl plik

@ -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)
}
}
*/

Wyświetl plik

@ -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)
} */
}

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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 }

Wyświetl plik

@ -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()
}
}
*/

Wyświetl plik

@ -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)
}
}
*/

Wyświetl plik

@ -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()
}
}
*/

Wyświetl plik

@ -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
}
*/

Wyświetl plik

@ -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
)
}
}
}

Wyświetl plik

@ -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])
}
}
*/

Wyświetl plik

@ -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() {
} */
}
}
*/

Wyświetl plik

@ -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)
) {
}
// }
}

Wyświetl plik

@ -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 {