From 9491a74fc69a8d3c8ca491d1f3c3fd9862a09ff5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 7 Apr 2020 09:36:12 -0700 Subject: [PATCH 01/24] wip - view layout kinda works --- app/build.gradle | 5 +- .../java/com/geeksville/mesh/MainActivity.kt | 97 ++++++++++++++++--- app/src/main/res/layout/activity_main.xml | 57 +++++++++++ app/src/main/res/layout/fragment_main.xml | 7 ++ 4 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/fragment_main.xml diff --git a/app/build.gradle b/app/build.gradle index 3ae0a59a7..0a2c91051 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,11 +73,12 @@ protobuf { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'com.google.android.material:material:1.0.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.viewpager2:viewpager2:1.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 549f69de2..0e0b1dec4 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -1,5 +1,6 @@ package com.geeksville.mesh +// import kotlinx.android.synthetic.main.tabs.* import android.Manifest import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothManager @@ -11,14 +12,18 @@ import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.MotionEvent +import android.view.* +import android.widget.FrameLayout import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.compose.Composable import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment import androidx.ui.core.setContent +import androidx.ui.foundation.Text +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.MessagesState @@ -26,18 +31,16 @@ import com.geeksville.mesh.model.NodeDB import com.geeksville.mesh.model.TextMessage import com.geeksville.mesh.model.UIState import com.geeksville.mesh.service.* -import com.geeksville.mesh.ui.AppStatus -import com.geeksville.mesh.ui.MeshApp import com.geeksville.mesh.ui.ScanState -import com.geeksville.mesh.ui.Screen import com.geeksville.util.Exceptions 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.tasks.Task +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator import java.nio.charset.Charset - /* UI design @@ -89,6 +92,42 @@ eventually: val utf8 = Charset.forName("UTF-8") +fun androidx.fragment.app.Fragment.setComposable( + id: Int, + content: @Composable() () -> Unit +): View? = + context?.let { + FrameLayout(it).apply { + 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) + } + } + +class ComposeFragment(id: Int, private val content: @Composable() () -> Unit) : Fragment(), + Logging { + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = + setComposable(id, content) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + debug("view w=${view.width}, h=${view.height}") + } + + override fun onStart() { + super.onStart() + debug("view w=${view?.width}, h=${view?.height}") + } +} + class MainActivity : AppCompatActivity(), Logging, ActivityCompat.OnRequestPermissionsResultCallback { @@ -105,6 +144,23 @@ class MainActivity : AppCompatActivity(), Logging, bluetoothManager.adapter } + private val tabsAdapter = object : FragmentStateAdapter(this) { + + override fun getItemCount(): Int = 2 + + override fun createFragment(position: Int): Fragment { + // Return a NEW fragment instance in createFragment(int) + /* + fragment.arguments = Bundle().apply { + // Our object is just an integer :-P + putInt(ARG_OBJECT, position + 1) + } */ + return ComposeFragment(position + 1) { + Text("Jetpack Compose") + } + } + } + private fun requestPermission() { debug("Checking permissions") @@ -139,7 +195,11 @@ class MainActivity : AppCompatActivity(), Logging, } // Ask for all the missing perms - ActivityCompat.requestPermissions(this, missingPerms.toTypedArray(), DID_REQUEST_PERM) + ActivityCompat.requestPermissions( + this, + missingPerms.toTypedArray(), + DID_REQUEST_PERM + ) // DID_REQUEST_PERM is an // app-defined int constant. The callback method gets the @@ -220,11 +280,20 @@ class MainActivity : AppCompatActivity(), Logging, // Handle any intent handleIntent(intent) - setContent { + /* setContent { MeshApp() - } + } */ + setContentView(R.layout.activity_main) + + val tab_layout = findViewById(R.id.tab_layout) + val pager = findViewById(R.id.pager) + pager.adapter = tabsAdapter + TabLayoutMediator(tab_layout, pager) { tab, position -> + tab.text = "OBJECT ${(position + 1)}" + }.attach() } + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleIntent(intent) @@ -384,7 +453,11 @@ class MainActivity : AppCompatActivity(), Logging, } MeshService.ACTION_MESH_CONNECTED -> { val connected = - MeshService.ConnectionState.valueOf(intent.getStringExtra(EXTRA_CONNECTED)!!) + MeshService.ConnectionState.valueOf( + intent.getStringExtra( + EXTRA_CONNECTED + )!! + ) onMeshConnectionChanged(connected) } else -> TODO() @@ -450,8 +523,10 @@ class MainActivity : AppCompatActivity(), Logging, bindMeshService() val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null + /* FIXME - not yet working if (!bonded) AppStatus.currentScreen = Screen.settings + */ } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..e7bab3fed --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml new file mode 100644 index 000000000..696be4e41 --- /dev/null +++ b/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file From 5403b150441fb33a8f3669e544b2d98dd50eb35c Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 7 Apr 2020 10:40:01 -0700 Subject: [PATCH 02/24] WIP is a little better --- .../java/com/geeksville/mesh/MainActivity.kt | 205 ++++++++++++------ .../java/com/geeksville/mesh/ui/Analytics.kt | 6 +- .../java/com/geeksville/mesh/ui/AppDrawer.kt | 19 +- .../com/geeksville/mesh/ui/BTScanScreen.kt | 4 +- .../java/com/geeksville/mesh/ui/Channel.kt | 1 - .../main/java/com/geeksville/mesh/ui/Map.kt | 5 +- .../java/com/geeksville/mesh/ui/MeshApp.kt | 10 +- .../java/com/geeksville/mesh/ui/Messages.kt | 2 - .../java/com/geeksville/mesh/ui/Status.kt | 6 +- .../main/java/com/geeksville/mesh/ui/Users.kt | 2 - app/src/main/res/layout/activity_main.xml | 3 + 11 files changed, 156 insertions(+), 107 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 0e0b1dec4..5653eccfc 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -21,9 +21,9 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.ui.core.setContent -import androidx.ui.foundation.Text import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 +import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.ServiceClient import com.geeksville.mesh.model.MessagesState @@ -31,7 +31,7 @@ import com.geeksville.mesh.model.NodeDB import com.geeksville.mesh.model.TextMessage import com.geeksville.mesh.model.UIState import com.geeksville.mesh.service.* -import com.geeksville.mesh.ui.ScanState +import com.geeksville.mesh.ui.* import com.geeksville.util.Exceptions import com.geeksville.util.exceptionReporter import com.google.android.gms.auth.api.signin.GoogleSignIn @@ -109,23 +109,31 @@ fun androidx.fragment.app.Fragment.setComposable( } } -class ComposeFragment(id: Int, private val content: @Composable() () -> Unit) : Fragment(), +/** + * A fragment that represents a current 'screen' in our app. + * + * Useful for tracking analytics + */ +open class ScreenFragment(private val screenName: String) : Fragment() { + override fun onResume() { + super.onResume() + GeeksvilleApplication.analytics.sendScreenView(screenName) + } + + override fun onPause() { + GeeksvilleApplication.analytics.endScreenView() + super.onPause() + } +} + +class ComposeFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) : + ScreenFragment(screenName), Logging { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = setComposable(id, content) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - debug("view w=${view.width}, h=${view.height}") - } - - override fun onStart() { - super.onStart() - debug("view w=${view?.width}, h=${view?.height}") - } } @@ -144,9 +152,56 @@ class MainActivity : AppCompatActivity(), Logging, bluetoothManager.adapter } - private val tabsAdapter = object : FragmentStateAdapter(this) { + /* + - override fun getItemCount(): Int = 2 + /-- + + + --> + */ + data class TabInfo(val text: String, val icon: Int, val content: Fragment) + + // 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( + "Messages", + R.drawable.ic_twotone_message_24, + ComposeFragment("Messages", 1) { MessagesContent() }), + TabInfo( + "Settings", + R.drawable.ic_twotone_settings_applications_24, + ComposeFragment("Settings", 2) { SettingsContent() }), + TabInfo( + "Users", + R.drawable.ic_twotone_people_24, + ComposeFragment("Users", 3) { UsersContent() }), + TabInfo( + "Channel", + R.drawable.ic_twotone_contactless_24, + ComposeFragment("Channel", 4) { ChannelContent(UIState.getChannel()) }), + TabInfo( + "Map", + R.drawable.ic_twotone_map_24, + ComposeFragment("Map", 5) { MapContent() }) + ) + + private + val tabsAdapter = object : FragmentStateAdapter(this) { + + override fun getItemCount(): Int = tabInfos.size override fun createFragment(position: Int): Fragment { // Return a NEW fragment instance in createFragment(int) @@ -155,9 +210,7 @@ class MainActivity : AppCompatActivity(), Logging, // Our object is just an integer :-P putInt(ARG_OBJECT, position + 1) } */ - return ComposeFragment(position + 1) { - Text("Jetpack Compose") - } + return tabInfos[position].content } } @@ -255,7 +308,11 @@ class MainActivity : AppCompatActivity(), Logging, startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) } } else { - Toast.makeText(this, "Error - this app requires bluetooth", Toast.LENGTH_LONG) + Toast.makeText( + this, + "Error - this app requires bluetooth", + Toast.LENGTH_LONG + ) .show() } @@ -289,7 +346,8 @@ class MainActivity : AppCompatActivity(), Logging, val pager = findViewById(R.id.pager) pager.adapter = tabsAdapter TabLayoutMediator(tab_layout, pager) { tab, position -> - tab.text = "OBJECT ${(position + 1)}" + tab.text = tabInfos[position].text + tab.icon = getDrawable(tabInfos[position].icon) }.attach() } @@ -323,7 +381,11 @@ class MainActivity : AppCompatActivity(), Logging, /** * Dispatch incoming result to the correct fragment. */ - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent? + ) { super.onActivityResult(requestCode, resultCode, data) // Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...); @@ -350,7 +412,8 @@ class MainActivity : AppCompatActivity(), Logging, } */ } - private var receiverRegistered = false + private + var receiverRegistered = false private fun registerMeshReceiver() { logAssert(!receiverRegistered) @@ -418,57 +481,67 @@ class MainActivity : AppCompatActivity(), Logging, } } - private val meshServiceReceiver = object : BroadcastReceiver() { + private + val meshServiceReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) = exceptionReporter { - debug("Received from mesh service $intent") + 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") + 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 { - NodeDB.nodes[it] = info - } - } - - 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 - // FIXME - don't just slam in a new list each time, it probably causes extra drawing. Figure out how to be Compose smarter... - val msg = TextMessage(sender, payload.toString(utf8)) - - MessagesState.addMessage(msg) + // We only care about nodes that have user info + info.user?.id?.let { + NodeDB.nodes[it] = info } - else -> TODO() } + + 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 + // FIXME - don't just slam in a new list each time, it probably causes extra drawing. Figure out how to be Compose smarter... + val msg = TextMessage( + sender, + payload.toString(utf8) + ) + + MessagesState.addMessage(msg) + } + else -> TODO() + } + } + MeshService.ACTION_MESH_CONNECTED -> { + val connected = + MeshService.ConnectionState.valueOf( + intent.getStringExtra( + EXTRA_CONNECTED + )!! + ) + onMeshConnectionChanged(connected) + } + else -> TODO() } - MeshService.ACTION_MESH_CONNECTED -> { - val connected = - MeshService.ConnectionState.valueOf( - intent.getStringExtra( - EXTRA_CONNECTED - )!! - ) - onMeshConnectionChanged(connected) - } - else -> TODO() } - } } - private val mesh = object : ServiceClient({ - com.geeksville.mesh.IMeshService.Stub.asInterface(it) - }) { + private + val mesh = object : + ServiceClient({ + com.geeksville.mesh.IMeshService.Stub.asInterface(it) + }) { override fun onConnected(service: com.geeksville.mesh.IMeshService) { UIState.meshService = service @@ -476,7 +549,8 @@ class MainActivity : AppCompatActivity(), Logging, registerMeshReceiver() // We won't receive a notify for the initial state of connection, so we force an update here - val connectionState = MeshService.ConnectionState.valueOf(service.connectionState()) + val connectionState = + MeshService.ConnectionState.valueOf(service.connectionState()) onMeshConnectionChanged(connectionState) debug("connected to mesh service, isConnected=${UIState.isConnected.value}") @@ -522,7 +596,8 @@ class MainActivity : AppCompatActivity(), Logging, bindMeshService() - val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null + val bonded = + RadioInterfaceService.getBondedDeviceAddress(this) != null /* FIXME - not yet working if (!bonded) AppStatus.currentScreen = Screen.settings diff --git a/app/src/main/java/com/geeksville/mesh/ui/Analytics.kt b/app/src/main/java/com/geeksville/mesh/ui/Analytics.kt index 57fedcb35..96e87da3f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Analytics.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Analytics.kt @@ -1,12 +1,10 @@ package com.geeksville.mesh.ui -import androidx.compose.Composable -import androidx.compose.onCommit -import com.geeksville.android.GeeksvilleApplication /** * Track compose screen visibility */ +/* @Composable fun analyticsScreen(name: String) { onCommit(AppStatus.currentScreen) { @@ -16,4 +14,4 @@ fun analyticsScreen(name: String) { GeeksvilleApplication.analytics.endScreenView() } } -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/AppDrawer.kt b/app/src/main/java/com/geeksville/mesh/ui/AppDrawer.kt index 4de02356f..c8e45ebd1 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/AppDrawer.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/AppDrawer.kt @@ -1,21 +1,6 @@ package com.geeksville.mesh.ui -import androidx.annotation.DrawableRes -import androidx.compose.Composable -import androidx.ui.core.Modifier -import androidx.ui.foundation.Text -import androidx.ui.foundation.shape.corner.RoundedCornerShape -import androidx.ui.graphics.Color -import androidx.ui.layout.* -import androidx.ui.material.Divider -import androidx.ui.material.MaterialTheme -import androidx.ui.material.Surface -import androidx.ui.material.TextButton -import androidx.ui.tooling.preview.Preview -import androidx.ui.unit.dp -import com.geeksville.mesh.R - - +/* @Composable fun AppDrawer( currentScreen: ScreenInfo, @@ -111,4 +96,4 @@ fun previewDrawer() { currentScreen = AppStatus.currentScreen, closeDrawer = { } ) -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt index 0cb0de09b..1e9a0b32e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt @@ -73,8 +73,8 @@ fun BTScanScreen() { val bluetoothAdapter = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter - analyticsScreen(name = "settings") - onCommit(AppStatus.currentScreen) { + // FIXME - remove onCommit now that we have a fragement to run in + onCommit() { ScanState.debug("BTScan component active") ScanUIState.selectedMacAddr = RadioInterfaceService.getBondedDeviceAddress(context) diff --git a/app/src/main/java/com/geeksville/mesh/ui/Channel.kt b/app/src/main/java/com/geeksville/mesh/ui/Channel.kt index d8b01d8d6..168dfd77a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Channel.kt @@ -23,7 +23,6 @@ object ChannelLog : Logging @Composable fun ChannelContent(channel: Channel?) { - analyticsScreen(name = "channel") val typography = MaterialTheme.typography val context = ContextAmbient.current diff --git a/app/src/main/java/com/geeksville/mesh/ui/Map.kt b/app/src/main/java/com/geeksville/mesh/ui/Map.kt index 1ebc95b8b..08b4e6f57 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Map.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Map.kt @@ -75,11 +75,10 @@ private val mapLifecycleCallbacks = object : Application.ActivityLifecycleCallba @Composable fun MapContent() { - analyticsScreen(name = "map") - val context = ContextAmbient.current - onCommit(AppStatus.currentScreen) { + // FIXME - remove onCommit + onCommit() { onDispose { // We no longer care about activity lifecycle (context.applicationContext as Application).unregisterActivityLifecycleCallbacks( diff --git a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt index 2641ef5c7..8958d63ba 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt @@ -1,19 +1,14 @@ package com.geeksville.mesh.ui -import androidx.compose.Composable -import androidx.compose.state -import androidx.ui.foundation.Text -import androidx.ui.material.* -import androidx.ui.tooling.preview.Preview +import androidx.ui.material.lightColorPalette import com.geeksville.android.Logging -import com.geeksville.mesh.R -import com.geeksville.mesh.model.UIState object UILog : Logging val palette = lightColorPalette() // darkColorPalette() +/* @Composable fun MeshApp() { val (drawerState, onDrawerStateChange) = state { DrawerState.Closed } @@ -73,3 +68,4 @@ private fun AppContent(openDrawer: () -> Unit) { } //} } +*/ \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/Messages.kt b/app/src/main/java/com/geeksville/mesh/ui/Messages.kt index 75e5d2981..ba71fa362 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Messages.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Messages.kt @@ -67,8 +67,6 @@ fun MessageCard(msg: TextMessage, modifier: Modifier = Modifier.None) { @Composable fun MessagesContent() { - analyticsScreen(name = "messages") - Column(modifier = LayoutSize.Fill) { val sidePad = 8.dp diff --git a/app/src/main/java/com/geeksville/mesh/ui/Status.kt b/app/src/main/java/com/geeksville/mesh/ui/Status.kt index dbe4bb64c..c20355c3a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Status.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Status.kt @@ -1,9 +1,6 @@ package com.geeksville.mesh.ui -import androidx.compose.Model -import com.geeksville.mesh.R - - +/* data class ScreenInfo(val icon: Int, val label: String) // defines the screens we have in the app @@ -28,3 +25,4 @@ object AppStatus { fun navigateTo(destination: ScreenInfo) { AppStatus.currentScreen = destination } +*/ \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/Users.kt b/app/src/main/java/com/geeksville/mesh/ui/Users.kt index 08d49e992..10abb0899 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Users.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Users.kt @@ -27,8 +27,6 @@ fun getInitials(name: String): String { @Composable fun UsersContent() { - analyticsScreen(name = "users") - Column { Row { fun connected() = UIState.isConnected.value != MeshService.ConnectionState.DISCONNECTED diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e7bab3fed..ae63bf094 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -32,6 +32,8 @@ Screen.channel -> ChannelContent(UIState.getChannel()) Screen.map -> MapContent() --> + /-- + + --> From 58e6f840ea1b3b213298b82cf611cf339cc9f33d Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 7 Apr 2020 11:27:51 -0700 Subject: [PATCH 03/24] map fragment kinda works --- .../java/com/geeksville/mesh/MainActivity.kt | 55 +---- .../com/geeksville/mesh/ui/ComposeFragment.kt | 38 ++++ .../main/java/com/geeksville/mesh/ui/Map.kt | 192 ++++++++---------- .../com/geeksville/mesh/ui/ScreenFragment.kt | 21 ++ app/src/main/res/layout/map_view.xml | 17 +- 5 files changed, 157 insertions(+), 166 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt create mode 100644 app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 5653eccfc..2dbd0c17f 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -12,18 +12,16 @@ import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle -import android.view.* -import android.widget.FrameLayout +import android.view.Menu +import android.view.MenuItem +import android.view.MotionEvent import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.compose.Composable import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment -import androidx.ui.core.setContent import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 -import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.android.ServiceClient import com.geeksville.mesh.model.MessagesState @@ -92,50 +90,6 @@ eventually: val utf8 = Charset.forName("UTF-8") -fun androidx.fragment.app.Fragment.setComposable( - id: Int, - content: @Composable() () -> Unit -): View? = - context?.let { - FrameLayout(it).apply { - 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) - } - } - -/** - * A fragment that represents a current 'screen' in our app. - * - * Useful for tracking analytics - */ -open class ScreenFragment(private val screenName: String) : Fragment() { - override fun onResume() { - super.onResume() - GeeksvilleApplication.analytics.sendScreenView(screenName) - } - - override fun onPause() { - GeeksvilleApplication.analytics.endScreenView() - super.onPause() - } -} - -class ComposeFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) : - ScreenFragment(screenName), - Logging { - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? = - setComposable(id, content) -} - class MainActivity : AppCompatActivity(), Logging, ActivityCompat.OnRequestPermissionsResultCallback { @@ -195,7 +149,8 @@ class MainActivity : AppCompatActivity(), Logging, TabInfo( "Map", R.drawable.ic_twotone_map_24, - ComposeFragment("Map", 5) { MapContent() }) + MapFragment() + ) ) private diff --git a/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt new file mode 100644 index 000000000..1986d4b08 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt @@ -0,0 +1,38 @@ +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 +import com.geeksville.android.Logging + +fun androidx.fragment.app.Fragment.setComposable( + id: Int, + content: @Composable() () -> Unit +): View? = + context?.let { + FrameLayout(it).apply { + 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) + } + } + + +class ComposeFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) : + ScreenFragment(screenName), + Logging { + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = + setComposable(id, content) +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/Map.kt b/app/src/main/java/com/geeksville/mesh/ui/Map.kt index 08b4e6f57..3c5934fd1 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Map.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Map.kt @@ -1,15 +1,10 @@ package com.geeksville.mesh.ui -import android.app.Activity -import android.app.Application import android.graphics.Color import android.os.Bundle -import androidx.compose.Composable -import androidx.compose.onCommit -import androidx.ui.core.ContextAmbient -import androidx.ui.fakeandroidview.AndroidView -import androidx.ui.material.MaterialTheme -import androidx.ui.tooling.preview.Preview +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import com.geeksville.android.Logging import com.geeksville.mesh.R import com.geeksville.mesh.model.NodeDB @@ -32,114 +27,65 @@ import com.mapbox.mapboxsdk.style.layers.SymbolLayer import com.mapbox.mapboxsdk.style.sources.GeoJsonSource -object mapLog : Logging +class MapFragment : ScreenFragment("Map"), Logging { + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.map_view, container, false) + lateinit var mapView: MapView -/** - * mapbox requires this, until compose has a nicer way of doing it, do it here - */ -private val mapLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { - var view: MapView? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - override fun onActivityPaused(activity: Activity) { - view?.onPause() - } + mapView = view.findViewById(R.id.mapView) + mapView.onCreate(UIState.savedInstanceState) - override fun onActivityStarted(activity: Activity) { - view?.onStart() - } + mapView.getMapAsync { map -> - override fun onActivityDestroyed(activity: Activity) { - view?.onDestroy() - } + // Find all nodes with valid locations + val nodesWithPosition = NodeDB.nodes.values.filter { it.validPosition != null } + val locations = nodesWithPosition.map { node -> + val p = node.position!! + debug("Showing on map: $node") - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { - view?.onSaveInstanceState(outState) - } + val f = Feature.fromGeometry( + Point.fromLngLat( + p.longitude, + p.latitude + ) + ) + node.user?.let { + f.addStringProperty("name", it.longName) + } + f + } + val nodeSourceId = "node-positions" + val nodeLayerId = "node-layer" + val labelLayerId = "label-layer" + val markerImageId = "my-marker-image" + val nodePositions = + GeoJsonSource(nodeSourceId, FeatureCollection.fromFeatures(locations)) - override fun onActivityStopped(activity: Activity) { - view?.onStop() - } + // val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24) + val markerIcon = activity!!.getDrawable(R.drawable.ic_twotone_person_pin_24)!! - /** - * Called when the Activity calls [super.onCreate()][Activity.onCreate]. - */ - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - } - - override fun onActivityResumed(activity: Activity) { - view?.onResume() - } -} - - -@Composable -fun MapContent() { - val context = ContextAmbient.current - - // FIXME - remove onCommit - onCommit() { - onDispose { - // We no longer care about activity lifecycle - (context.applicationContext as Application).unregisterActivityLifecycleCallbacks( - mapLifecycleCallbacks + val nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId).withProperties( + iconImage(markerImageId), + iconAnchor(Property.ICON_ANCHOR_BOTTOM), + iconAllowOverlap(true) ) - mapLifecycleCallbacks.view = null - } - } - // Find all nodes with valid locations - val nodesWithPosition = NodeDB.nodes.values.filter { it.validPosition != null } - val locations = nodesWithPosition.map { node -> - val p = node.position!! - mapLog.debug("Showing on map: $node") - - val f = Feature.fromGeometry( - Point.fromLngLat( - p.longitude, - p.latitude + val labelLayer = SymbolLayer(labelLayerId, nodeSourceId).withProperties( + textField(Expression.get("name")), + textSize(12f), + textColor(Color.RED), + textVariableAnchor(arrayOf(TEXT_ANCHOR_TOP)), + textJustify(TEXT_JUSTIFY_AUTO), + textAllowOverlap(true) ) - ) - node.user?.let { - f.addStringProperty("name", it.longName) - } - f - } - val nodeSourceId = "node-positions" - val nodeLayerId = "node-layer" - val labelLayerId = "label-layer" - val markerImageId = "my-marker-image" - val nodePositions = - GeoJsonSource(nodeSourceId, FeatureCollection.fromFeatures(locations)) - // val markerIcon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_twotone_person_pin_24) - val markerIcon = context.getDrawable(R.drawable.ic_twotone_person_pin_24)!! - - val nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId).withProperties( - iconImage(markerImageId), - iconAnchor(Property.ICON_ANCHOR_BOTTOM), - iconAllowOverlap(true) - ) - - val labelLayer = SymbolLayer(labelLayerId, nodeSourceId).withProperties( - textField(Expression.get("name")), - textSize(12f), - textColor(Color.RED), - textVariableAnchor(arrayOf(TEXT_ANCHOR_TOP)), - textJustify(TEXT_JUSTIFY_AUTO), - textAllowOverlap(true) - ) - - AndroidView(R.layout.map_view) { view -> - view as MapView - view.onCreate(UIState.savedInstanceState) - - mapLifecycleCallbacks.view = view - (context.applicationContext as Application).registerActivityLifecycleCallbacks( - mapLifecycleCallbacks - ) - - view.getMapAsync { map -> map.setStyle(Style.OUTDOORS) { style -> style.addSource(nodePositions) style.addImage(markerImageId, markerIcon) @@ -173,14 +119,38 @@ fun MapContent() { } } } -} + override fun onPause() { + mapView.onPause() + super.onPause() + } -@Preview -@Composable -fun previewMap() { - // another bug? It seems modaldrawerlayout not yet supported in preview - MaterialTheme(colors = palette) { - MapContent() + override fun onStart() { + super.onStart() + mapView.onStart() + } + + override fun onStop() { + mapView.onStop() + super.onStop() + } + + override fun onResume() { + super.onResume() + mapView.onResume() + } + + override fun onDestroy() { + mapView.onDestroy() + super.onDestroy() + } + + override fun onSaveInstanceState(outState: Bundle) { + mapView.onSaveInstanceState(outState) + super.onSaveInstanceState(outState) } } + + + + diff --git a/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt new file mode 100644 index 000000000..d707ce881 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/ScreenFragment.kt @@ -0,0 +1,21 @@ +package com.geeksville.mesh.ui + +import androidx.fragment.app.Fragment +import com.geeksville.android.GeeksvilleApplication + +/** + * A fragment that represents a current 'screen' in our app. + * + * Useful for tracking analytics + */ +open class ScreenFragment(private val screenName: String) : Fragment() { + override fun onResume() { + super.onResume() + GeeksvilleApplication.analytics.sendScreenView(screenName) + } + + override fun onPause() { + GeeksvilleApplication.analytics.endScreenView() + super.onPause() + } +} diff --git a/app/src/main/res/layout/map_view.xml b/app/src/main/res/layout/map_view.xml index a77c3ecf9..bfdb57e79 100644 --- a/app/src/main/res/layout/map_view.xml +++ b/app/src/main/res/layout/map_view.xml @@ -1,10 +1,17 @@ - + android:id="@+id/mapFrame"> + + + + \ No newline at end of file From 7af7ec084399aeb2a44fde8104a1fd3dd42b5b00 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 7 Apr 2020 12:13:50 -0700 Subject: [PATCH 04/24] map node zooming works better --- .../java/com/geeksville/mesh/MainActivity.kt | 14 +- .../com/geeksville/mesh/ui/BTScanScreen.kt | 14 +- .../com/geeksville/mesh/ui/ComposeFragment.kt | 10 +- .../mesh/ui/{Map.kt => MapFragment.kt} | 143 ++++++++++-------- 4 files changed, 103 insertions(+), 78 deletions(-) rename app/src/main/java/com/geeksville/mesh/ui/{Map.kt => MapFragment.kt} (54%) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 2dbd0c17f..113b63d58 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -134,10 +134,7 @@ class MainActivity : AppCompatActivity(), Logging, "Messages", R.drawable.ic_twotone_message_24, ComposeFragment("Messages", 1) { MessagesContent() }), - TabInfo( - "Settings", - R.drawable.ic_twotone_settings_applications_24, - ComposeFragment("Settings", 2) { SettingsContent() }), + TabInfo( "Users", R.drawable.ic_twotone_people_24, @@ -150,7 +147,11 @@ class MainActivity : AppCompatActivity(), Logging, "Map", R.drawable.ic_twotone_map_24, MapFragment() - ) + ), + TabInfo( + "Settings", + R.drawable.ic_twotone_settings_applications_24, + BTScanFragment("Settings", 2) { SettingsContent() }) ) private @@ -301,7 +302,7 @@ class MainActivity : AppCompatActivity(), Logging, val pager = findViewById(R.id.pager) pager.adapter = tabsAdapter TabLayoutMediator(tab_layout, pager) { tab, position -> - tab.text = tabInfos[position].text + // tab.text = tabInfos[position].text // I think it looks better with icons only tab.icon = getDrawable(tabInfos[position].icon) }.attach() } @@ -539,7 +540,6 @@ class MainActivity : AppCompatActivity(), Logging, } override fun onStop() { - ScanState.stopScan() unregisterMeshReceiver() // No point in receiving updates while the GUI is gone, we'll get them when the user launches the activity unbindMeshService() diff --git a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt index 1e9a0b32e..1935b7d5e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/BTScanScreen.kt @@ -65,6 +65,15 @@ data class BTScanEntry(val name: String, val macAddress: String, val bonded: Boo } +class BTScanFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) : + ComposeFragment(screenName, id, content) { + + override fun onStop() { + ScanState.stopScan() + super.onStop() + } +} + @Composable fun BTScanScreen() { val context = ContextAmbient.current @@ -146,11 +155,6 @@ fun BTScanScreen() { ScanState.callback = scanCallback } } - - onDispose { - ScanState.debug("BTScan component deactivated") - ScanState.stopScan() - } } Column { diff --git a/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt index 1986d4b08..28e7f0793 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt @@ -7,7 +7,6 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.Composable import androidx.ui.core.setContent -import com.geeksville.android.Logging fun androidx.fragment.app.Fragment.setComposable( id: Int, @@ -27,9 +26,12 @@ fun androidx.fragment.app.Fragment.setComposable( } -class ComposeFragment(screenName: String, id: Int, private val content: @Composable() () -> Unit) : - ScreenFragment(screenName), - Logging { +open class ComposeFragment( + screenName: String, + id: Int, + private val content: @Composable() () -> Unit +) : + ScreenFragment(screenName) { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? diff --git a/app/src/main/java/com/geeksville/mesh/ui/Map.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt similarity index 54% rename from app/src/main/java/com/geeksville/mesh/ui/Map.kt rename to app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 3c5934fd1..e7ef92a17 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Map.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -17,6 +17,7 @@ import com.mapbox.mapboxsdk.camera.CameraUpdateFactory import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.geometry.LatLngBounds import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.Style import com.mapbox.mapboxsdk.style.expressions.Expression import com.mapbox.mapboxsdk.style.layers.Property @@ -28,7 +29,81 @@ import com.mapbox.mapboxsdk.style.sources.GeoJsonSource class MapFragment : ScreenFragment("Map"), Logging { - + + private val nodeSourceId = "node-positions" + private val nodeLayerId = "node-layer" + private val labelLayerId = "label-layer" + private val markerImageId = "my-marker-image" + + private val nodePositions = GeoJsonSource(nodeSourceId) + + private val nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId).withProperties( + iconImage(markerImageId), + iconAnchor(Property.ICON_ANCHOR_BOTTOM), + iconAllowOverlap(true) + ) + + private val labelLayer = SymbolLayer(labelLayerId, nodeSourceId).withProperties( + textField(Expression.get("name")), + textSize(12f), + textColor(Color.RED), + textVariableAnchor(arrayOf(TEXT_ANCHOR_TOP)), + textJustify(TEXT_JUSTIFY_AUTO), + 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 + + 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) + } + f + } + + return FeatureCollection.fromFeatures(locations) + } + + 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) }) + + 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) + } + map.animateCamera(update, 1000) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -44,48 +119,9 @@ class MapFragment : ScreenFragment("Map"), Logging { mapView.getMapAsync { map -> - // Find all nodes with valid locations - val nodesWithPosition = NodeDB.nodes.values.filter { it.validPosition != null } - 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) - } - f - } - val nodeSourceId = "node-positions" - val nodeLayerId = "node-layer" - val labelLayerId = "label-layer" - val markerImageId = "my-marker-image" - val nodePositions = - GeoJsonSource(nodeSourceId, FeatureCollection.fromFeatures(locations)) - // 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 nodeLayer = SymbolLayer(nodeLayerId, nodeSourceId).withProperties( - iconImage(markerImageId), - iconAnchor(Property.ICON_ANCHOR_BOTTOM), - iconAllowOverlap(true) - ) - - val labelLayer = SymbolLayer(labelLayerId, nodeSourceId).withProperties( - textField(Expression.get("name")), - textSize(12f), - textColor(Color.RED), - textVariableAnchor(arrayOf(TEXT_ANCHOR_TOP)), - textJustify(TEXT_JUSTIFY_AUTO), - textAllowOverlap(true) - ) - map.setStyle(Style.OUTDOORS) { style -> style.addSource(nodePositions) style.addImage(markerImageId, markerIcon) @@ -95,28 +131,6 @@ class MapFragment : ScreenFragment("Map"), Logging { //map.uiSettings.isScrollGesturesEnabled = true //map.uiSettings.isZoomGesturesEnabled = true - - if (nodesWithPosition.isNotEmpty()) { - val update = if (nodesWithPosition.size >= 2) { - // Multiple nodes, make them all fit on the map view - val bounds = LatLngBounds.Builder() - - // Add all positions - bounds.includes(nodesWithPosition.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!! - - val cameraPos = CameraPosition.Builder().target( - LatLng(it.latitude, it.longitude) - ).zoom(9.0).build() - CameraUpdateFactory.newCameraPosition(cameraPos) - } - map.animateCamera(update, 1000) - } } } @@ -138,6 +152,11 @@ 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() { From 606dc0fd07359a9da31f90baabceb670bfcab000 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 7 Apr 2020 12:32:42 -0700 Subject: [PATCH 05/24] turn off gestures - they are confusing with the map view --- app/src/main/java/com/geeksville/mesh/MainActivity.kt | 2 ++ .../java/com/geeksville/mesh/service/RadioInterfaceService.kt | 2 +- app/src/main/res/layout/activity_main.xml | 4 +--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 113b63d58..e64a7c2cf 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -301,6 +301,8 @@ class MainActivity : AppCompatActivity(), Logging, val tab_layout = findViewById(R.id.tab_layout) val pager = findViewById(R.id.pager) pager.adapter = tabsAdapter + pager.isUserInputEnabled = + false // Gestures for screen switching doesn't work so good with the map view TabLayoutMediator(tab_layout, pager) { tab, position -> // tab.text = tabInfos[position].text // I think it looks better with icons only tab.icon = getDrawable(tabInfos[position].icon) diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index c4d2ab131..e6dddbc4c 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -316,7 +316,7 @@ class RadioInterfaceService : Service(), Logging { debug("requested MTU result=$mtuRes") mtuRes.getOrThrow() // FIXME - why sometimes is the result Unit!?! - fromNum = service.getCharacteristic(BTM_FROMNUM_CHARACTER) + fromNum = service.getCharacteristic(BTM_FROMNUM_CHARACTER)!! // We must set this to true before broadcasting connectionChanged isConnected = true diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ae63bf094..c850876d6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -30,9 +30,7 @@ Screen.settings -> SettingsContent() Screen.users -> UsersContent() Screen.channel -> ChannelContent(UIState.getChannel()) - Screen.map -> MapContent() --> - - /-- + Screen.map -> MapContent() Date: Tue, 7 Apr 2020 12:48:42 -0700 Subject: [PATCH 06/24] fighting compose --- .../com/geeksville/mesh/ui/ComposeFragment.kt | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt index 28e7f0793..dad14ba5a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ComposeFragment.kt @@ -14,6 +14,7 @@ fun androidx.fragment.app.Fragment.setComposable( ): 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 @@ -30,11 +31,26 @@ open class ComposeFragment( screenName: String, id: Int, private val content: @Composable() () -> Unit -) : - ScreenFragment(screenName) { +) : ScreenFragment(screenName) { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = - setComposable(id, content) -} \ No newline at end of file + 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) + } */ +} From c70f29640639cd81b71237d0c4a774ec105c3628 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 7 Apr 2020 14:31:21 -0700 Subject: [PATCH 07/24] experimenting with android studio --- .idea/render.experimental.xml | 6 ++++++ app/build.gradle | 2 +- app/src/main/res/layout/channel_fragment.xml | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .idea/render.experimental.xml create mode 100644 app/src/main/res/layout/channel_fragment.xml diff --git a/.idea/render.experimental.xml b/.idea/render.experimental.xml new file mode 100644 index 000000000..dde5d2b09 --- /dev/null +++ b/.idea/render.experimental.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 0a2c91051..e1fe106c7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.2.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4' implementation 'com.google.android.material:material:1.1.0' implementation 'androidx.viewpager2:viewpager2:1.0.0' testImplementation 'junit:junit:4.12' diff --git a/app/src/main/res/layout/channel_fragment.xml b/app/src/main/res/layout/channel_fragment.xml new file mode 100644 index 000000000..1cc2727d6 --- /dev/null +++ b/app/src/main/res/layout/channel_fragment.xml @@ -0,0 +1,14 @@ + + + +