kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
commit
b62a5a7946
|
@ -12,6 +12,8 @@ apply plugin: 'com.google.firebase.crashlytics'
|
|||
// protobuf
|
||||
apply plugin: 'com.google.protobuf'
|
||||
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
/*
|
||||
signingConfigs {
|
||||
|
@ -105,6 +107,9 @@ protobuf {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
def room_version = "2.2.5"
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
|
@ -117,7 +122,15 @@ dependencies {
|
|||
implementation 'androidx.viewpager2:viewpager2:1.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
|
||||
// optional - Kotlin Extensions and Coroutines support for Room
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
|
||||
// optional - Test helpers
|
||||
testImplementation "androidx.room:room-testing:$room_version"
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
|
|
|
@ -30,6 +30,8 @@ import androidx.appcompat.widget.Toolbar
|
|||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.geeksville.android.GeeksvilleApplication
|
||||
|
@ -917,6 +919,15 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
Toast.makeText(applicationContext, item.title, Toast.LENGTH_SHORT).show()
|
||||
return true
|
||||
}
|
||||
R.id.debug -> {
|
||||
val fragmentManager: FragmentManager = supportFragmentManager
|
||||
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
|
||||
val nameFragment = DebugFragment()
|
||||
fragmentTransaction.add(R.id.mainActivityLayout, nameFragment)
|
||||
fragmentTransaction.addToBackStack(null)
|
||||
fragmentTransaction.commit()
|
||||
return true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package com.geeksville.mesh.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
|
||||
@Database(entities = [Packet::class], version = 1, exportSchema = false)
|
||||
abstract class MeshtasticDatabase : RoomDatabase() {
|
||||
abstract fun packetDao(): PacketDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: MeshtasticDatabase? = null
|
||||
|
||||
fun getDatabase(
|
||||
context: Context
|
||||
): MeshtasticDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
MeshtasticDatabase::class.java,
|
||||
"meshtastic_database"
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
INSTANCE = instance
|
||||
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.geeksville.mesh.database
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
|
||||
class PacketRepository(private val packetDao : PacketDao) {
|
||||
val allPackets : LiveData<List<Packet>> = packetDao.getAllPacket(500)
|
||||
|
||||
suspend fun insert(packet: Packet) {
|
||||
packetDao.insert(packet)
|
||||
}
|
||||
|
||||
suspend fun deleteAll() {
|
||||
packetDao.deleteAll()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.geeksville.mesh.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
|
||||
@Dao
|
||||
interface PacketDao {
|
||||
|
||||
@Query("Select * from packet order by rowid desc limit 0,:maxItem")
|
||||
fun getAllPacket(maxItem: Int): LiveData<List<Packet>>
|
||||
|
||||
@Insert
|
||||
fun insert(packet: Packet)
|
||||
|
||||
@Query("DELETE from packet")
|
||||
fun deleteAll()
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.geeksville.mesh.database.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
|
||||
@Entity(tableName = "packet")
|
||||
data class Packet(@PrimaryKey val uuid: String,
|
||||
@ColumnInfo(name = "type") val message_type: String,
|
||||
@ColumnInfo(name = "received_date") val received_date: Long,
|
||||
@ColumnInfo(name = "message") val raw_message: String
|
||||
) {
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -8,13 +8,20 @@ import android.os.RemoteException
|
|||
import android.view.Menu
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.android.BuildUtils.isEmulator
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.IMeshService
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.MyNodeInfo
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/// Given a human name, strip out the first letter of the first three words and return that as the initials for
|
||||
/// that user. If the original name is only one word, strip vowels from the original name and if the result is
|
||||
|
@ -37,10 +44,27 @@ fun getInitials(nameIn: String): String {
|
|||
}
|
||||
|
||||
class UIViewModel(app: Application) : AndroidViewModel(app), Logging {
|
||||
|
||||
private val repository: PacketRepository
|
||||
|
||||
val allPackets: LiveData<List<Packet>>
|
||||
|
||||
init {
|
||||
val packetsDao = MeshtasticDatabase.getDatabase(app).packetDao()
|
||||
repository = PacketRepository(packetsDao)
|
||||
allPackets = repository.allPackets
|
||||
debug("ViewModel created")
|
||||
}
|
||||
|
||||
|
||||
fun insertPacket(packet: Packet) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.insert(packet)
|
||||
}
|
||||
|
||||
fun deleteAllPacket() = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.deleteAll()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Return the current channel info
|
||||
|
|
|
@ -28,6 +28,9 @@ import com.geeksville.mesh.*
|
|||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.ToRadio
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.util.*
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.common.api.ResolvableApiException
|
||||
|
@ -163,6 +166,8 @@ class MeshService : Service(), Logging {
|
|||
/// The current state of our connection
|
||||
private var connectionState = ConnectionState.DISCONNECTED
|
||||
|
||||
private var packetRepo: PacketRepository? = null
|
||||
|
||||
/*
|
||||
see com.geeksville.mesh broadcast intents
|
||||
// RECEIVED_OPAQUE for data received from other nodes
|
||||
|
@ -481,6 +486,9 @@ class MeshService : Service(), Logging {
|
|||
|
||||
info("Creating mesh service")
|
||||
|
||||
val packetsDao = MeshtasticDatabase.getDatabase(applicationContext).packetDao()
|
||||
packetRepo = PacketRepository(packetsDao)
|
||||
|
||||
// Switch to the IO thread
|
||||
serviceScope.handledLaunch {
|
||||
loadSettings() // Load our last known node DB
|
||||
|
@ -964,6 +972,8 @@ class MeshService : Service(), Logging {
|
|||
// debug("Recieved: $packet")
|
||||
val p = packet.decoded
|
||||
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "packet", System.currentTimeMillis(), packet.toString())
|
||||
insertPacket(packetToSave)
|
||||
// If the rxTime was not set by the device (because device software was old), guess at a time
|
||||
val rxTime = if (packet.rxTime != 0) packet.rxTime else currentSecond()
|
||||
|
||||
|
@ -995,6 +1005,13 @@ class MeshService : Service(), Logging {
|
|||
handleAckNak(false, p.failId)
|
||||
}
|
||||
|
||||
private fun insertPacket(packetToSave: Packet) {
|
||||
serviceScope.handledLaunch {
|
||||
info("insert: ${packetToSave.message_type} = ${packetToSave.raw_message.toOneLineString()}")
|
||||
packetRepo!!.insert(packetToSave)
|
||||
}
|
||||
}
|
||||
|
||||
private fun currentSecond() = (System.currentTimeMillis() / 1000).toInt()
|
||||
|
||||
|
||||
|
@ -1229,6 +1246,8 @@ class MeshService : Service(), Logging {
|
|||
|
||||
|
||||
private fun handleRadioConfig(radio: MeshProtos.RadioConfig) {
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "RadioConfig", System.currentTimeMillis(), radio.toString())
|
||||
insertPacket(packetToSave)
|
||||
radioConfig = radio
|
||||
}
|
||||
|
||||
|
@ -1257,6 +1276,9 @@ class MeshService : Service(), Logging {
|
|||
private fun handleNodeInfo(info: MeshProtos.NodeInfo) {
|
||||
debug("Received nodeinfo num=${info.num}, hasUser=${info.hasUser()}, hasPosition=${info.hasPosition()}")
|
||||
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "NodeInfo", System.currentTimeMillis(), info.toString())
|
||||
insertPacket(packetToSave)
|
||||
|
||||
logAssert(newNodes.size <= 256) // Sanity check to make sure a device bug can't fill this list forever
|
||||
newNodes.add(info)
|
||||
}
|
||||
|
@ -1266,6 +1288,9 @@ class MeshService : Service(), Logging {
|
|||
* Update the nodeinfo (called from either new API version or the old one)
|
||||
*/
|
||||
private fun handleMyInfo(myInfo: MeshProtos.MyNodeInfo) {
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "MyNodeInfo", System.currentTimeMillis(), myInfo.toString())
|
||||
insertPacket(packetToSave)
|
||||
|
||||
setFirmwareUpdateFilename(myInfo)
|
||||
|
||||
val mi = with(myInfo) {
|
||||
|
@ -1312,6 +1337,10 @@ class MeshService : Service(), Logging {
|
|||
|
||||
private fun handleConfigComplete(configCompleteId: Int) {
|
||||
if (configCompleteId == configNonce) {
|
||||
|
||||
val packetToSave = Packet(UUID.randomUUID().toString(), "ConfigComplete", System.currentTimeMillis(), configCompleteId.toString())
|
||||
insertPacket(packetToSave)
|
||||
|
||||
// This was our config request
|
||||
if (newMyNodeInfo == null || newNodes.isEmpty())
|
||||
errormsg("Did not receive a valid config")
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import kotlinx.android.synthetic.main.debug_fragment.*
|
||||
|
||||
class DebugFragment : Fragment() {
|
||||
|
||||
val model: UIViewModel by viewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
|
||||
return inflater.inflate(R.layout.debug_fragment, container, false)
|
||||
}
|
||||
//Button to clear All log
|
||||
|
||||
//List all log
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val recyclerView = view.findViewById<RecyclerView>(R.id.packets_recyclerview)
|
||||
val adapter = PacketListAdapter(requireContext())
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
|
||||
clearButton.setOnClickListener {
|
||||
model.deleteAllPacket()
|
||||
}
|
||||
|
||||
closeButton.setOnClickListener{
|
||||
parentFragmentManager.popBackStack();
|
||||
}
|
||||
model.allPackets.observe(viewLifecycleOwner, Observer {
|
||||
packets -> packets?.let { adapter.setPackets(it) }
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import android.content.Context
|
||||
import java.text.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import java.util.*
|
||||
|
||||
class PacketListAdapter internal constructor(
|
||||
context: Context
|
||||
) : RecyclerView.Adapter<PacketListAdapter.PacketViewHolder>() {
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
private var packets = emptyList<Packet>()
|
||||
|
||||
private val timeFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM)
|
||||
|
||||
inner class PacketViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val packetTypeView: TextView = itemView.findViewById(R.id.type)
|
||||
val packetDateReceivedView: TextView = itemView.findViewById(R.id.dateReceived)
|
||||
val packetRawMessage : TextView = itemView.findViewById(R.id.rawMessage)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PacketViewHolder {
|
||||
val itemView = inflater.inflate(R.layout.adapter_packet_layout, parent, false)
|
||||
return PacketViewHolder(itemView)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PacketViewHolder, position: Int) {
|
||||
val current = packets[position]
|
||||
holder.packetTypeView.text = current.message_type
|
||||
holder.packetRawMessage.text = current.raw_message
|
||||
val date = Date(current.received_date)
|
||||
holder.packetDateReceivedView.text = timeFormat.format(date)
|
||||
}
|
||||
|
||||
internal fun setPackets(packets: List<Packet>) {
|
||||
this.packets = packets
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemCount() = packets.size
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M8,13H10.55V10H13.45V13H16L12,17L8,13M19.35,10.04C21.95,10.22 24,12.36 24,15A5,5 0 0,1 19,20H6A6,6 0 0,1 0,14C0,10.91 2.34,8.36 5.35,8.04C6.6,5.64 9.11,4 12,4C15.64,4 18.67,6.59 19.35,10.04M19,18A3,3 0 0,0 22,15C22,13.45 20.78,12.14 19.22,12.04L17.69,11.93L17.39,10.43C16.88,7.86 14.62,6 12,6C9.94,6 8.08,7.14 7.13,8.97L6.63,9.92L5.56,10.03C3.53,10.24 2,11.95 2,14A4,4 0 0,0 6,18H19Z" />
|
||||
</vector>
|
|
@ -1,90 +1,100 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
tools:context=".MainActivity"
|
||||
android:id="@+id/mainActivityLayout">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView5"
|
||||
android:layout_width="wrap_content"
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:contentDescription="@string/application_icon"
|
||||
android:scaleType="center"
|
||||
android:scaleX="1.5"
|
||||
android:scaleY="1.5"
|
||||
app:srcCompat="@drawable/ic_baseline_settings_input_antenna_24" />
|
||||
android:background="?attr/colorPrimary"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:id="@+id/imageView5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:contentDescription="@string/application_icon"
|
||||
android:scaleType="center"
|
||||
android:scaleX="1.5"
|
||||
android:scaleY="1.5"
|
||||
app:srcCompat="@drawable/ic_baseline_settings_input_antenna_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:gravity="center"
|
||||
android:minHeight="?actionBarSize"
|
||||
android:padding="16dp"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Screen.messages -> MessagesContent()
|
||||
Screen.settings -> SettingsContent()
|
||||
Screen.users -> UsersContent()
|
||||
Screen.channel -> ChannelContent(UIState.getChannel())
|
||||
Screen.map -> MapContent()
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:icon="@drawable/ic_twotone_message_24"
|
||||
android:text="Messages"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:gravity="center"
|
||||
android:minHeight="?actionBarSize"
|
||||
android:padding="16dp"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title" />
|
||||
android:layout_width="wrap_content" />
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:icon="@drawable/ic_twotone_settings_applications_24"
|
||||
android:text="Settings"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content" />
|
||||
-->
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
<!-- Screen.messages -> MessagesContent()
|
||||
Screen.settings -> SettingsContent()
|
||||
Screen.users -> UsersContent()
|
||||
Screen.channel -> ChannelContent(UIState.getChannel())
|
||||
Screen.map -> MapContent()
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:icon="@drawable/ic_twotone_message_24"
|
||||
android:text="Messages"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content" />
|
||||
|
||||
<com.google.android.material.tabs.TabItem
|
||||
android:icon="@drawable/ic_twotone_settings_applications_24"
|
||||
android:text="Settings"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content" />
|
||||
-->
|
||||
|
||||
</com.google.android.material.tabs.TabLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</FrameLayout>
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.App.CardView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/type"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="node_info"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateReceived"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="09-17 21:00:58.641"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/cloudDownloadIcon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/cloudDownloadIcon" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rawMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="monospace"
|
||||
android:text="TextView"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="8sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/type" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cloudDownloadIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:alpha="0.4"
|
||||
app:layout_constraintEnd_toStartOf="@+id/dateReceived"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/cloud_download_outline_24" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FFFFFF">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/packets_recyclerview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/clearButton" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/clearButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/clear_last_messages"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/closeButton"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:backgroundTint="#D1D1D1"
|
||||
app:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
app:iconPadding="0dp"
|
||||
app:iconSize="24dp"
|
||||
app:iconTint="#000000"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/Meshtastic.Button.Rounded" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/debug_last_messages"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/packets_recyclerview"
|
||||
app:layout_constraintEnd_toStartOf="@+id/clearButton"
|
||||
app:layout_constraintStart_toEndOf="@+id/closeButton"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -7,6 +7,10 @@
|
|||
android:tint="#FFFFFF"
|
||||
android:title="@string/disconnected"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/debug"
|
||||
android:title="@string/debug_panel"
|
||||
app:showAsAction="withText" />
|
||||
<item
|
||||
android:id="@+id/about"
|
||||
android:title="@string/about"
|
||||
|
|
Ładowanie…
Reference in New Issue