kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Merge pull request #360 from mcumings/hilt-db-init
Introduce Hilt dependency injectionpull/370/head
commit
413a6b9d52
|
@ -2,6 +2,7 @@ apply plugin: 'com.android.application'
|
|||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.github.triplet.play'
|
||||
apply plugin: 'de.mobilej.unmock'
|
||||
|
@ -135,7 +136,9 @@ dependencies {
|
|||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||
|
||||
|
||||
// optional - Kotlin Extensions and Coroutines support for Room
|
||||
|
@ -200,3 +203,7 @@ dependencies {
|
|||
|
||||
implementation project(':geeksville-androidlib')
|
||||
}
|
||||
|
||||
kapt {
|
||||
correctErrorTypes true
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object ApplicationModule {
|
||||
@Provides
|
||||
fun provideSharedPreferences(application: Application): SharedPreferences {
|
||||
return application.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
|
||||
}
|
||||
}
|
|
@ -62,6 +62,7 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.vorlonsoft.android.rate.AppRate
|
||||
import com.vorlonsoft.android.rate.StoreType
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -123,6 +124,7 @@ eventually:
|
|||
|
||||
val utf8 = Charset.forName("UTF-8")
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(), Logging,
|
||||
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
|
||||
|
|
|
@ -7,8 +7,13 @@ import com.geeksville.android.GeeksvilleApplication
|
|||
import com.geeksville.android.Logging
|
||||
import com.geeksville.util.Exceptions
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
class MeshUtilApplication : GeeksvilleApplication() {
|
||||
// NOTE: This is a workaround since the Hilt Gradle plugin doesn't support constructors with default parameters
|
||||
open class GeeksvilleApplicationWrapper : GeeksvilleApplication()
|
||||
|
||||
@HiltAndroidApp
|
||||
class MeshUtilApplication : GeeksvilleApplicationWrapper() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package com.geeksville.mesh.database
|
||||
|
||||
import android.app.Application
|
||||
import androidx.room.Room
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
class DatabaseModule {
|
||||
@Provides
|
||||
fun provideDatabase(application: Application): MeshtasticDatabase {
|
||||
return Room.databaseBuilder(
|
||||
application.applicationContext,
|
||||
MeshtasticDatabase::class.java,
|
||||
"meshtastic_database"
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providePacketDao(database: MeshtasticDatabase): PacketDao {
|
||||
return database.packetDao()
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
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
|
||||
|
@ -10,27 +8,4 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +1,34 @@
|
|||
package com.geeksville.mesh.database
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class PacketRepository(private val packetDao : PacketDao) {
|
||||
val allPackets : LiveData<List<Packet>> = packetDao.getAllPacket(MAX_ITEMS)
|
||||
val allPacketsInReceiveOrder : Flow<List<Packet>> = packetDao.getAllPacketsInReceiveOrder(MAX_ITEMS)
|
||||
class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Lazy<PacketDao>) {
|
||||
private val packetDao by lazy {
|
||||
packetDaoLazy.get()
|
||||
}
|
||||
|
||||
suspend fun insert(packet: Packet) {
|
||||
suspend fun getAllPackets(): Flow<List<Packet>> = withContext(Dispatchers.IO) {
|
||||
packetDao.getAllPacket(MAX_ITEMS)
|
||||
}
|
||||
|
||||
suspend fun getAllPacketsInReceiveOrder(): Flow<List<Packet>> = withContext(Dispatchers.IO) {
|
||||
packetDao.getAllPacketsInReceiveOrder(MAX_ITEMS)
|
||||
}
|
||||
|
||||
suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) {
|
||||
packetDao.insert(packet)
|
||||
}
|
||||
|
||||
suspend fun deleteAll() {
|
||||
suspend fun deleteAll() = withContext(Dispatchers.IO) {
|
||||
packetDao.deleteAll()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_ITEMS = 500
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.geeksville.mesh.database.dao
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
|
@ -11,7 +10,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
interface PacketDao {
|
||||
|
||||
@Query("Select * from packet order by received_date desc limit 0,:maxItem")
|
||||
fun getAllPacket(maxItem: Int): LiveData<List<Packet>>
|
||||
fun getAllPacket(maxItem: Int): Flow<List<Packet>>
|
||||
|
||||
@Query("Select * from packet order by received_date asc limit 0,:maxItem")
|
||||
fun getAllPacketsInReceiveOrder(maxItem: Int): Flow<List<Packet>>
|
||||
|
|
|
@ -7,18 +7,20 @@ import android.net.Uri
|
|||
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.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.mesh.*
|
||||
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 com.geeksville.mesh.ui.positionToMeter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -26,6 +28,7 @@ import java.io.BufferedWriter
|
|||
import java.io.FileWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/// Given a human name, strip out the first letter of the first three words and return that as the initials for
|
||||
|
@ -51,20 +54,25 @@ fun getInitials(nameIn: String): String {
|
|||
return initials.take(nchars)
|
||||
}
|
||||
|
||||
class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging {
|
||||
@HiltViewModel
|
||||
class UIViewModel @Inject constructor(
|
||||
private val app: Application,
|
||||
private val repository: PacketRepository,
|
||||
private val preferences: SharedPreferences
|
||||
) : ViewModel(), Logging {
|
||||
|
||||
private val repository: PacketRepository
|
||||
|
||||
val allPackets: LiveData<List<Packet>>
|
||||
private val _allPacketState = MutableStateFlow<List<Packet>>(emptyList())
|
||||
val allPackets: StateFlow<List<Packet>> = _allPacketState
|
||||
|
||||
init {
|
||||
val packetsDao = MeshtasticDatabase.getDatabase(app).packetDao()
|
||||
repository = PacketRepository(packetsDao)
|
||||
allPackets = repository.allPackets
|
||||
viewModelScope.launch {
|
||||
repository.getAllPackets().collect { packets ->
|
||||
_allPacketState.value = packets
|
||||
}
|
||||
}
|
||||
debug("ViewModel created")
|
||||
}
|
||||
|
||||
|
||||
fun insertPacket(packet: Packet) = viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.insert(packet)
|
||||
}
|
||||
|
@ -78,8 +86,6 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging
|
|||
context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private val context: Context get() = app.applicationContext
|
||||
|
||||
var actionBarMenu: Menu? = null
|
||||
|
||||
var meshService: IMeshService? = null
|
||||
|
@ -208,7 +214,7 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging
|
|||
channels.value =
|
||||
c // Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings)
|
||||
|
||||
getPreferences(context).edit(commit = true) {
|
||||
preferences.edit(commit = true) {
|
||||
this.putString("channel-url", c.getChannelUrl().toString())
|
||||
}
|
||||
}
|
||||
|
@ -226,11 +232,11 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging
|
|||
val bluetoothEnabled = object : MutableLiveData<Boolean>(false) {
|
||||
}
|
||||
|
||||
val provideLocation = object : MutableLiveData<Boolean>(getPreferences(context).getBoolean(MyPreferences.provideLocationKey, false)) {
|
||||
val provideLocation = object : MutableLiveData<Boolean>(preferences.getBoolean(MyPreferences.provideLocationKey, false)) {
|
||||
override fun setValue(value: Boolean) {
|
||||
super.setValue(value)
|
||||
|
||||
getPreferences(context).edit(commit = true) {
|
||||
preferences.edit(commit = true) {
|
||||
this.putBoolean(MyPreferences.provideLocationKey, value)
|
||||
}
|
||||
}
|
||||
|
@ -246,7 +252,7 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging
|
|||
ownerName.value = s
|
||||
|
||||
// note: we allow an empty userstring to be written to prefs
|
||||
getPreferences(context).edit(commit = true) {
|
||||
preferences.edit(commit = true) {
|
||||
putString("owner", s)
|
||||
}
|
||||
}
|
||||
|
@ -286,7 +292,7 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging
|
|||
// our device in localNodePosition.
|
||||
var localNodePosition: MeshProtos.Position? = null
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd,HH:mm:ss", Locale.getDefault())
|
||||
repository.allPacketsInReceiveOrder.first().forEach { packet ->
|
||||
repository.getAllPacketsInReceiveOrder().first().forEach { packet ->
|
||||
packet.proto?.let { proto ->
|
||||
packet.position?.let { position ->
|
||||
if (proto.from == myNodeNum) {
|
||||
|
|
|
@ -22,7 +22,6 @@ import com.geeksville.mesh.*
|
|||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.MeshProtos.ToRadio
|
||||
import com.geeksville.mesh.android.hasBackgroundPermission
|
||||
import com.geeksville.mesh.database.MeshtasticDatabase
|
||||
import com.geeksville.mesh.database.PacketRepository
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
|
@ -36,9 +35,12 @@ import com.google.android.gms.location.LocationServices
|
|||
import com.google.android.gms.location.LocationSettingsRequest
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import dagger.Lazy
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.max
|
||||
|
||||
|
@ -49,7 +51,10 @@ import kotlin.math.max
|
|||
* Note: this service will go away once all clients are unbound from it.
|
||||
* Warning: do not override toString, it causes infinite recursion on some androids (because contextWrapper.getResources calls to string
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MeshService : Service(), Logging {
|
||||
@Inject
|
||||
lateinit var packetRepository: Lazy<PacketRepository>
|
||||
|
||||
companion object : Logging {
|
||||
|
||||
|
@ -119,9 +124,6 @@ class MeshService : Service(), Logging {
|
|||
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||
private var connectionState = ConnectionState.DISCONNECTED
|
||||
|
||||
/// A database of received packets - used only for debug log
|
||||
private var packetRepo: PacketRepository? = null
|
||||
|
||||
private var fusedLocationClient: FusedLocationProviderClient? = null
|
||||
|
||||
// If we've ever read a valid region code from our device it will be here
|
||||
|
@ -326,9 +328,6 @@ 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
|
||||
|
@ -994,7 +993,7 @@ class MeshService : Service(), Logging {
|
|||
serviceScope.handledLaunch {
|
||||
// Do not log, because might contain PII
|
||||
// info("insert: ${packetToSave.message_type} = ${packetToSave.raw_message.toOneLineString()}")
|
||||
packetRepo!!.insert(packetToSave)
|
||||
packetRepository.get().insert(packetToSave)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import com.geeksville.android.Logging
|
||||
import com.geeksville.android.hideKeyboard
|
||||
import com.geeksville.mesh.R
|
||||
|
@ -16,7 +15,9 @@ import com.geeksville.mesh.model.UIViewModel
|
|||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.util.exceptionToSnackbar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
|
||||
private val MAX_INT_DEVICE = 0xFFFFFFFF
|
||||
private var _binding: AdvancedSettingsBinding? = null
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.security.SecureRandom
|
||||
|
||||
|
||||
|
@ -51,6 +52,7 @@ fun ImageView.setOpaque() {
|
|||
imageAlpha = 255
|
||||
}
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ChannelFragment : ScreenFragment("Channel"), Logging {
|
||||
|
||||
private var _binding: ChannelFragmentBinding? = null
|
||||
|
|
|
@ -6,13 +6,15 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.databinding.DebugFragmentBinding
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DebugFragment : Fragment() {
|
||||
|
||||
private var _binding: DebugFragmentBinding? = null
|
||||
|
@ -48,8 +50,8 @@ class DebugFragment : Fragment() {
|
|||
binding.closeButton.setOnClickListener {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
model.allPackets.observe(viewLifecycleOwner, Observer { packets ->
|
||||
model.allPackets.asLiveData().observe(viewLifecycleOwner) { packets ->
|
||||
packets?.let { adapter.setPackets(it) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,8 +32,10 @@ import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource
|
|||
import com.mapbox.maps.plugin.animation.MapAnimationOptions
|
||||
import com.mapbox.maps.plugin.animation.flyTo
|
||||
import com.mapbox.maps.plugin.gestures.gestures
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MapFragment : ScreenFragment("Map"), Logging {
|
||||
|
||||
private val model: UIViewModel by activityViewModels()
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.geeksville.mesh.databinding.MessagesFragmentBinding
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.google.android.material.chip.Chip
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
|
@ -41,6 +42,7 @@ fun EditText.on(actionId: Int, func: () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MessagesFragment : ScreenFragment("Messages"), Logging {
|
||||
|
||||
private var _binding: MessagesFragmentBinding? = null
|
||||
|
|
|
@ -49,6 +49,7 @@ import com.google.android.gms.location.LocationSettingsRequest
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -439,6 +440,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@AndroidEntryPoint
|
||||
class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||
private var _binding: SettingsFragmentBinding? = null
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.view.ViewGroup
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.geeksville.android.Logging
|
||||
|
@ -19,9 +18,11 @@ import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding
|
|||
import com.geeksville.mesh.databinding.NodelistFragmentBinding
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.util.formatAgo
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.net.URLEncoder
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@AndroidEntryPoint
|
||||
class UsersFragment : ScreenFragment("Users"), Logging {
|
||||
|
||||
private var _binding: NodelistFragmentBinding? = null
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
ext.coroutines_version = "1.5.2"
|
||||
ext.hilt_version = '2.40.5'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
|
@ -30,6 +31,8 @@ buildscript {
|
|||
|
||||
// for unit testing https://github.com/bjoernQ/unmock-plugin
|
||||
classpath 'com.github.bjoernq:unmockplugin:0.7.9'
|
||||
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue