Prepare patch step WIP

This commit is contained in:
jbr7rr 2023-03-11 20:19:06 +01:00
parent 8f6dfbdf88
commit 7afa0beb0a
21 changed files with 750 additions and 88 deletions

View file

@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<application>
<activity android:name=".ui.MedtrumActivity" />
<service
android:name=".services.MedtrumService"
android:enabled="true"

View file

@ -79,7 +79,6 @@ class MedtrumPlugin @Inject constructor(
private var medtrumService: MedtrumService? = null
private var mPumpType: PumpType = PumpType.MEDTRUM_NANO
private val mPumpDescription = PumpDescription(mPumpType)
private var mDeviceSN: Long = 0
override fun onStart() {
super.onStart()
@ -90,16 +89,6 @@ class MedtrumPlugin @Inject constructor(
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ context.unbindService(mConnection) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event ->
if (event.isChanged(rh.gs(info.nightscout.pump.medtrum.R.string.key_snInput))) {
pumpSync.connectNewPump()
changePump()
}
}, fabricPrivacy::logException)
changePump()
}
override fun onStop() {
@ -122,15 +111,8 @@ class MedtrumPlugin @Inject constructor(
}
}
fun changePump() {
aapsLogger.debug(LTag.PUMP, "changePump: called!")
try {
mDeviceSN = sp.getString(info.nightscout.pump.medtrum.R.string.key_snInput, " ").toLong(radix = 16)
} catch (e: NumberFormatException) {
aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!")
}
// TODO: add medtrumPump.reset()
commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.device_changed), null)
fun getService(): MedtrumService? {
return medtrumService
}
override fun isInitialized(): Boolean {
@ -138,14 +120,17 @@ class MedtrumPlugin @Inject constructor(
}
override fun isSuspended(): Boolean {
return false
return true
}
override fun isBusy(): Boolean {
return false
return true
}
override fun isConnected(): Boolean {
return if (!isInitialized()) true else medtrumService?.isConnected ?: true // This is a workaround to prevent AAPS to trigger connects when we are initializing
}
override fun isConnected(): Boolean = medtrumService?.isConnected ?: false
override fun isConnecting(): Boolean = medtrumService?.isConnecting ?: false
override fun isHandshakeInProgress(): Boolean = false
@ -153,19 +138,23 @@ class MedtrumPlugin @Inject constructor(
}
override fun connect(reason: String) {
aapsLogger.debug(LTag.PUMP, "Medtrum connect - reason:$reason")
aapsLogger.debug(LTag.PUMP, "Medtrum connect - service::$medtrumService")
aapsLogger.debug(LTag.PUMP, "Medtrum connect - mDeviceSN:$mDeviceSN")
if (medtrumService != null && mDeviceSN != 0.toLong()) {
aapsLogger.debug(LTag.PUMP, "Medtrum connect - Attempt connection!")
val success = medtrumService?.connect(reason, mDeviceSN) ?: false
if (!success) ToastUtils.errorToast(context, info.nightscout.core.ui.R.string.ble_not_supported_or_not_paired)
if (isInitialized()) {
aapsLogger.debug(LTag.PUMP, "Medtrum connect - reason:$reason")
aapsLogger.debug(LTag.PUMP, "Medtrum connect - service::$medtrumService")
// aapsLogger.debug(LTag.PUMP, "Medtrum connect - mDeviceSN:$mDeviceSN")
if (medtrumService != null) {
aapsLogger.debug(LTag.PUMP, "Medtrum connect - Attempt connection!")
val success = medtrumService?.connect(reason) ?: false
if (!success) ToastUtils.errorToast(context, info.nightscout.core.ui.R.string.ble_not_supported_or_not_paired)
}
}
}
override fun disconnect(reason: String) {
aapsLogger.debug(LTag.PUMP, "RS disconnect from: $reason")
medtrumService?.disconnect(reason)
if (isInitialized()) {
aapsLogger.debug(LTag.PUMP, "Medtrum disconnect from: $reason")
medtrumService?.disconnect(reason)
}
}
override fun stopConnecting() {
@ -173,11 +162,13 @@ class MedtrumPlugin @Inject constructor(
}
override fun getPumpStatus(reason: String) {
medtrumService?.readPumpStatus()
if (isInitialized()) {
medtrumService?.readPumpStatus()
}
}
override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
return PumpEnactResult(injector) // TODO
return PumpEnactResult(injector).success(true).enacted(true) // TODO
}
override fun isThisProfileSet(profile: Profile): Boolean {

View file

@ -0,0 +1,11 @@
package info.nightscout.pump.medtrum.code
enum class EventType {
ACTIVATION_CLICKED,
DEACTIVATION_CLICKED,
INVALID_BASAL_RATE,
PROFILE_NOT_SET,
FINISH_ACTIVITY,
SHOW_DISCARD_DIALOG
;
}

View file

@ -0,0 +1,18 @@
package info.nightscout.pump.medtrum.code
enum class PatchStep {
SAFE_DEACTIVATION,
MANUALLY_TURNING_OFF_ALARM,
DISCARDED,
DISCARDED_FOR_CHANGE,
DISCARDED_FROM_ALARM,
PREPARE_PATCH,
PRIME,
ATTACH_INSERT_NEEDLE,
BASAL_SCHEDULE,
CHECK_CONNECTION,
CANCEL,
COMPLETE,
BACK_TO_HOME,
FINISH;
}

View file

@ -7,17 +7,21 @@ import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoMap
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPreparePatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment
import info.nightscout.pump.medtrum.services.MedtrumService
import info.nightscout.pump.medtrum.ui.MedtrumActivity
import info.nightscout.pump.medtrum.ui.MedtrumOverviewFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
import info.nightscout.pump.medtrum.ui.viewmodel.ViewModelFactory
import info.nightscout.pump.medtrum.ui.viewmodel.ViewModelKey
import javax.inject.Provider
@Module
@Suppress("unused")
abstract class MedtrumModule {
companion object {
@Provides
@ -34,11 +38,29 @@ abstract class MedtrumModule {
@ViewModelKey(MedtrumOverviewViewModel::class)
internal abstract fun bindsMedtrumOverviewViewmodel(viewModel: MedtrumOverviewViewModel): ViewModel
@Binds
@IntoMap
@MedtrumPluginQualifier
@ViewModelKey(MedtrumViewModel::class)
internal abstract fun bindsMedtrumViewModel(viewModel: MedtrumViewModel): ViewModel
// FRAGMENTS
@ContributesAndroidInjector
abstract fun contributesMedtrumOverviewFragment(): MedtrumOverviewFragment
@FragmentScope
@ContributesAndroidInjector
internal abstract fun contributesPreparePatchFragment(): MedtrumPreparePatchFragment
@FragmentScope
@ContributesAndroidInjector
internal abstract fun contributesPrimeFragment(): MedtrumPrimeFragment
// ACTIVITIES
@ContributesAndroidInjector
abstract fun contributesMedtrumActivity(): MedtrumActivity
// SERVICE
@ContributesAndroidInjector
abstract fun contributesDanaRSService(): MedtrumService
@ContributesAndroidInjector
abstract fun contributesMedtrumService(): MedtrumService
}

View file

@ -0,0 +1,19 @@
package info.nightscout.pump.medtrum.extension
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
fun AppCompatActivity.replaceFragmentInActivity(fragment: Fragment, frameId: Int, addToBackStack: Boolean = false) {
supportFragmentManager.transact {
replace(frameId, fragment)
if (addToBackStack) addToBackStack(null)
}
}
private inline fun FragmentManager.transact(action: FragmentTransaction.() -> Unit) {
beginTransaction().apply {
action()
}.commit()
}

View file

@ -49,7 +49,9 @@ import javax.inject.Inject
import javax.inject.Singleton
interface BLECommCallback {
fun onBLEConnected()
fun onBLEDisconnected()
fun onNotification(notification: ByteArray)
fun onIndication(indication: ByteArray)
fun onSendMessageError(reason: String)
@ -88,8 +90,8 @@ class BLEComm @Inject internal constructor(
private val mBluetoothAdapter: BluetoothAdapter? get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
private var mBluetoothGatt: BluetoothGatt? = null
var isConnected = false
var isConnecting = false
var isConnected = false // TODO: These may be removed have no function
var isConnecting = false// TODO: These may be removed have no function
private var uartWrite: BluetoothGattCharacteristic? = null
private var uartRead: BluetoothGattCharacteristic? = null
@ -179,7 +181,6 @@ class BLEComm @Inject internal constructor(
}
aapsLogger.debug(LTag.PUMPBTCOMM, "disconnect from: $from")
mBluetoothGatt?.disconnect()
mBluetoothGatt = null
}
@Synchronized
@ -373,7 +374,7 @@ class BLEComm @Inject internal constructor(
close()
isConnected = false
isConnecting = false
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
mCallback?.onBLEDisconnected()
aapsLogger.debug(LTag.PUMPBTCOMM, "Device was disconnected " + gatt.device.name) //Device was disconnected
}
}

View file

@ -32,6 +32,7 @@ import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventInitializationChanged
import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.events.EventPreferenceChange
import info.nightscout.rx.events.EventProfileSwitchChanged
import info.nightscout.rx.events.EventPumpStatusChanged
import info.nightscout.rx.logging.AAPSLogger
@ -79,6 +80,8 @@ class MedtrumService : DaggerService(), BLECommCallback {
private var mDeviceSN: Long = 0
private var currentState: State = IdleState()
var isConnected = false
var isConnecting = false
// TODO: Stuff like this in a settings class?
private var mLastDeviceTime: Long = 0
@ -90,6 +93,16 @@ class MedtrumService : DaggerService(), BLECommCallback {
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ stopSelf() }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event ->
if (event.isChanged(rh.gs(info.nightscout.pump.medtrum.R.string.key_snInput))) {
pumpSync.connectNewPump()
changePump()
}
}, fabricPrivacy::logException)
changePump()
}
override fun onDestroy() {
@ -97,26 +110,27 @@ class MedtrumService : DaggerService(), BLECommCallback {
super.onDestroy()
}
val isConnected: Boolean
get() = bleComm.isConnected
val isConnecting: Boolean
get() = bleComm.isConnecting
fun connect(from: String, deviceSN: Long): Boolean {
// TODO Check we might want to replace this with start scan?
mDeviceSN = deviceSN
return bleComm.connect(from, deviceSN)
fun connect(from: String): Boolean {
aapsLogger.debug(LTag.PUMP, "connect: called!")
if (currentState is IdleState) {
isConnecting = true
isConnected = false
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING))
return bleComm.connect(from, mDeviceSN)
} else {
aapsLogger.error(LTag.PUMPCOMM, "Connect attempt when in non Idle state from: " + from)
return false
}
}
fun stopConnecting() {
// TODO proper way for this might need send commands etc
// bleComm.stopConnecting()
bleComm.stopConnecting()
}
fun disconnect(from: String) {
// TODO proper way for this might need send commands etc
// bleComm.disconnect(from)
bleComm.disconnect(from)
}
fun readPumpStatus() {
@ -156,11 +170,30 @@ class MedtrumService : DaggerService(), BLECommCallback {
return false
}
fun changePump() {
aapsLogger.debug(LTag.PUMP, "changePump: called!")
try {
mDeviceSN = sp.getString(info.nightscout.pump.medtrum.R.string.key_snInput, " ").toLong(radix = 16)
} catch (e: NumberFormatException) {
aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!")
}
// TODO: What do we do with active patch here?
when (currentState) {
// is IdleState -> connect("changePump")
// is ReadyState -> disconnect("changePump")
else -> null // TODO: What to do here? Abort stuff?
}
}
/** BLECommCallbacks */
override fun onBLEConnected() {
currentState.onConnected()
}
override fun onBLEDisconnected() {
currentState.onDisconnected()
}
override fun onNotification(notification: ByteArray) {
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onNotification" + notification.contentToString())
// TODO
@ -207,14 +240,34 @@ class MedtrumService : DaggerService(), BLECommCallback {
aapsLogger.debug(LTag.PUMPCOMM, "onIndicationr: " + this.toString() + "Should not be called here!")
}
open fun onConnected() {}
open fun onConnected() {
aapsLogger.debug(LTag.PUMPCOMM, "onConnected")
}
open fun onDisconnected() {
aapsLogger.debug(LTag.PUMPCOMM, "onDisconnected")
isConnecting = false
isConnected = false
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
// TODO: Check flow for this
toState(IdleState())
}
}
private inner class IdleState : State() {
override fun onEnter() {}
override fun onConnected() {
super.onConnected()
toState(AuthState())
}
override fun onDisconnected() {
super.onDisconnected()
// TODO replace this by proper connecting state where we can retry
}
}
private inner class AuthState : State() {
@ -391,7 +444,11 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached ReadyState!")
// Now we are fully connected and authenticated and we can start sending commands. Let AAPS know
isConnecting = false
isConnected = true
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED))
}
// Just a placeholder, this state is reached when the patch is ready to receive commands (Bolus, temp basal and whatever)
}
}
}

View file

@ -0,0 +1,97 @@
package info.nightscout.pump.medtrum.ui
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.media.RingtoneManager
import android.os.Bundle
import android.view.MotionEvent
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPreparePatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment
import info.nightscout.core.utils.extensions.safeGetSerializableExtra
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.databinding.ActivityMedtrumBinding
import info.nightscout.pump.medtrum.extension.replaceFragmentInActivity
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
class MedtrumActivity : MedtrumBaseActivity<ActivityMedtrumBinding>() {
override fun getLayoutId(): Int = R.layout.activity_medtrum
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.actionMasked == MotionEvent.ACTION_UP) {
// TODO
}
return super.dispatchTouchEvent(event)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.apply {
viewModel = ViewModelProvider(this@MedtrumActivity, viewModelFactory).get(MedtrumViewModel::class.java)
viewModel?.apply {
processIntent(intent)
patchStep.observe(this@MedtrumActivity) {
when (it) {
PatchStep.PREPARE_PATCH -> setupViewFragment(MedtrumPreparePatchFragment.newInstance())
PatchStep.PRIME -> setupViewFragment(MedtrumPrimeFragment.newInstance())
PatchStep.CANCEL -> this@MedtrumActivity.finish()
else -> Unit
}
}
}
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
processIntent(intent)
}
private fun processIntent(intent: Intent?) {
binding.viewModel?.apply {
intent?.run {
val step = intent.safeGetSerializableExtra(EXTRA_START_PATCH_STEP, PatchStep::class.java)
initializePatchStep(step)
}
}
}
override fun onDestroy() {
super.onDestroy()
// TODO
}
override fun onBackPressed() {
binding.viewModel?.apply {
// TODO DEACTIVATION ?
}
}
companion object {
const val EXTRA_START_PATCH_STEP = "EXTRA_START_PATCH_FRAGMENT_UI"
const val EXTRA_START_FROM_MENU = "EXTRA_START_FROM_MENU"
@JvmStatic
fun createIntentFromMenu(context: Context, patchStep: PatchStep): Intent {
return Intent(context, MedtrumActivity::class.java).apply {
putExtra(EXTRA_START_PATCH_STEP, patchStep)
putExtra(EXTRA_START_FROM_MENU, true)
}
}
}
private fun setupViewFragment(baseFragment: MedtrumBaseFragment<*>) {
replaceFragmentInActivity(baseFragment, R.id.framelayout_fragment, false)
}
}

View file

@ -6,18 +6,18 @@ import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import info.nightscout.pump.medtrum.databinding.MedtrumOverviewFragmentBinding
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumOverviewBinding
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.EventType
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.logging.AAPSLogger
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
class MedtrumOverviewFragment : MedtrumBaseFragment<MedtrumOverviewFragmentBinding>() {
class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBinding>() {
@Inject lateinit var rxBus: RxBus
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var aapsLogger: AAPSLogger
private lateinit var resultLauncherForResume: ActivityResultLauncher<Intent>
@ -25,7 +25,7 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<MedtrumOverviewFragmentBindi
private var disposable: CompositeDisposable = CompositeDisposable()
override fun getLayoutId(): Int = R.layout.medtrum_overview_fragment
override fun getLayoutId(): Int = R.layout.fragment_medtrum_overview
override fun onDestroy() {
super.onDestroy()
@ -38,18 +38,23 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<MedtrumOverviewFragmentBindi
binding.apply {
viewmodel = ViewModelProvider(this@MedtrumOverviewFragment, viewModelFactory).get(MedtrumOverviewViewModel::class.java)
viewmodel?.apply {
// TODO Handle events here, see eopatch eventhandler
}
resultLauncherForResume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
when (it.resultCode) {
// TODO Handle events here, see eopatch eventhandler
eventHandler.observe(viewLifecycleOwner) { evt ->
when (evt.peekContent()) {
EventType.ACTIVATION_CLICKED -> requireContext().apply { startActivity(MedtrumActivity.createIntentFromMenu(this, PatchStep.PREPARE_PATCH)) }
else -> Unit
}
}
}
resultLauncherForPause = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
when (it.resultCode) {
// TODO Handle events here, see eopatch eventhandler
resultLauncherForResume = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
when (it.resultCode) {
// TODO Handle events here, see eopatch eventhandler
}
}
resultLauncherForPause = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
when (it.resultCode) {
// TODO Handle events here, see eopatch eventhandler
}
}
}
}

View file

@ -0,0 +1,42 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPreparePatchBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import javax.inject.Inject
class MedtrumPreparePatchFragment : MedtrumBaseFragment<FragmentMedtrumPreparePatchBinding>() {
@Inject lateinit var aapsLogger: AAPSLogger
companion object {
fun newInstance(): MedtrumPreparePatchFragment = MedtrumPreparePatchFragment()
}
override fun getLayoutId(): Int = R.layout.fragment_medtrum_prepare_patch
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
aapsLogger.debug(LTag.PUMP, "MedtrumPreparePatchFragment onViewCreated")
binding.apply {
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(MedtrumViewModel::class.java)
viewModel?.apply {
setupStep.observe(viewLifecycleOwner) {
when (it) {
// TODO: Confirmation dialog
MedtrumViewModel.SetupStep.CONNECTED -> btnPositive.visibility = View.VISIBLE
else -> Unit
}
}
preparePatch()
}
}
}
}

View file

@ -0,0 +1,32 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPrimeBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import javax.inject.Inject
class MedtrumPrimeFragment : MedtrumBaseFragment<FragmentMedtrumPrimeBinding>() {
@Inject lateinit var aapsLogger: AAPSLogger
companion object {
fun newInstance(): MedtrumPrimeFragment = MedtrumPrimeFragment()
}
override fun getLayoutId(): Int = R.layout.fragment_medtrum_prime
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(MedtrumViewModel::class.java)
// TODO do stuff
}
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.pump.medtrum.ui.event
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.annotation.MainThread
import java.util.concurrent.atomic.AtomicBoolean
open class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
@MainThread
fun call() {
value = null
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.pump.medtrum.ui.event
open class UIEvent<out T>(private val content: T) {
var value: Any? = null
fun peekContent(): T = content
}

View file

@ -1,32 +1,77 @@
package info.nightscout.pump.medtrum.ui.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.pump.medtrum.code.EventType
import info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
import info.nightscout.pump.medtrum.ui.event.UIEvent
import info.nightscout.pump.medtrum.ui.viewmodel.BaseViewModel
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventPumpStatusChanged
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class MedtrumOverviewViewModel @Inject constructor(
private val aapsLogger: AAPSLogger
private val aapsLogger: AAPSLogger,
private val rxBus: RxBus,
private val aapsSchedulers: AapsSchedulers,
private val fabricPrivacy: FabricPrivacy,
private val profileFunction: ProfileFunction,
) : BaseViewModel<MedtrumBaseNavigator>() {
val isPatchActivated : Boolean
private var disposable: CompositeDisposable = CompositeDisposable()
private val _eventHandler = SingleLiveEvent<UIEvent<EventType>>()
val eventHandler: LiveData<UIEvent<EventType>>
get() = _eventHandler
private val _bleStatus = SingleLiveEvent<String>()
val bleStatus: LiveData<String>
get() = _bleStatus
// TODO make these livedata
val isPatchActivated: Boolean
get() = false // TODO
val isPatchConnected: Boolean
get() = false // TODO
val bleStatus : String
get() ="" //TODO
init {
// TODO
disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
_bleStatus.value = when (it.status) {
EventPumpStatusChanged.Status.CONNECTING ->
"{fa-bluetooth-b spin} ${it.secondsElapsed}s"
EventPumpStatusChanged.Status.CONNECTED ->
"{fa-bluetooth}"
EventPumpStatusChanged.Status.DISCONNECTED ->
"{fa-bluetooth-b}"
else ->
""
}
}, fabricPrivacy::logException)
}
fun onClickActivation(){
fun onClickActivation() {
aapsLogger.debug(LTag.PUMP, "Start Patch clicked!")
// TODO
val profile = profileFunction.getProfile()
if (profile == null) {
_eventHandler.postValue(UIEvent(EventType.PROFILE_NOT_SET))
} else {
_eventHandler.postValue(UIEvent(EventType.ACTIVATION_CLICKED))
}
}
fun onClickDeactivation(){
fun onClickDeactivation() {
aapsLogger.debug(LTag.PUMP, "Stop Patch clicked!")
// TODO
}

View file

@ -0,0 +1,128 @@
package info.nightscout.pump.medtrum.ui.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.medtrum.services.MedtrumService
import info.nightscout.pump.medtrum.code.EventType
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
import info.nightscout.pump.medtrum.ui.event.UIEvent
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventPumpStatusChanged
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class MedtrumViewModel @Inject constructor(
private val rh: ResourceHelper,
private val aapsLogger: AAPSLogger,
private val rxBus: RxBus,
private val aapsSchedulers: AapsSchedulers,
private val fabricPrivacy: FabricPrivacy,
private val medtrumPlugin: MedtrumPlugin,
private val sp: SP
) : BaseViewModel<MedtrumBaseNavigator>() {
val patchStep = MutableLiveData<PatchStep>()
val title = "Activation"
val medtrumService: MedtrumService?
get() = medtrumPlugin.getService()
private var disposable: CompositeDisposable = CompositeDisposable()
private val _eventHandler = SingleLiveEvent<UIEvent<EventType>>()
val eventHandler: LiveData<UIEvent<EventType>>
get() = _eventHandler
private var mInitPatchStep: PatchStep? = null
init {
disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
when (it.status) {
EventPumpStatusChanged.Status.CONNECTING -> {}
EventPumpStatusChanged.Status.CONNECTED
-> if (patchStep.value == PatchStep.PREPARE_PATCH) setupStep.postValue(SetupStep.CONNECTED) else {
}
EventPumpStatusChanged.Status.DISCONNECTED -> {}
else -> {}
}
}, fabricPrivacy::logException)
}
fun moveStep(newPatchStep: PatchStep) {
val oldPatchStep = patchStep.value
if (oldPatchStep != newPatchStep) {
when (newPatchStep) {
PatchStep.CANCEL -> {
if (medtrumService?.isConnected == true || medtrumService?.isConnecting == true) medtrumService?.disconnect("Cancel") else {
}
}
else -> null
}?.let {
// TODO Update lifecycle
}
}
prepareStep(newPatchStep)
aapsLogger.info(LTag.PUMP, "moveStep: $oldPatchStep -> $newPatchStep")
}
fun initializePatchStep(step: PatchStep?) {
mInitPatchStep = prepareStep(step)
}
fun preparePatch() {
// TODO: Decide if we want to connect already when user is still filling, or if we want to wait after user is done filling
medtrumService?.connect("PreparePatch")
}
private fun prepareStep(step: PatchStep?): PatchStep {
// TODO Title per screen :) And proper sync with patchstate
// (step ?: convertToPatchStep(patchConfig.lifecycleEvent.lifeCycle)).let { newStep ->
(step ?: PatchStep.SAFE_DEACTIVATION).let { newStep ->
when (newStep) {
else -> ""
}.let {
}
patchStep.postValue(newStep)
return newStep
}
}
enum class SetupStep {
CONNECTED,
PRIME_READY,
ACTIVATED
}
val setupStep = MutableLiveData<SetupStep>()
private fun updateSetupStep(newSetupStep: SetupStep) {
aapsLogger.info(LTag.PUMP, "curSetupStep: ${setupStep.value}, newSetupStep: $newSetupStep")
setupStep.postValue(newSetupStep)
}
}

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<import type="info.nightscout.pump.medtrum.code.PatchStep" />
<variable
name="viewModel"
type="info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:orientation="vertical"
tools:context=".ui.MedtrumActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_toolbar"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@{viewModel.title}"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/string_change_patch" />
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/framelayout_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="15dp" />
</LinearLayout>
</layout>

View file

@ -3,9 +3,10 @@
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel"/>
type="info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel" />
</data>
<RelativeLayout
@ -100,15 +101,16 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<Button
android:id="@+id/button1"
style="@style/ButtonMediumFontStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/string_new_patch"
android:enabled="@{!viewmodel.isPatchActivated}"
app:onSafeClick="@{() -> viewmodel.onClickActivation()}"/>
android:text="@string/string_new_patch"
app:onSafeClick="@{() -> viewmodel.onClickActivation()}" />
<Button
android:id="@+id/button2"
@ -116,9 +118,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/string_stop_patch"
android:enabled="@{viewmodel.isPatchActivated}"
app:onSafeClick="@{() -> viewmodel.onClickDeactivation()}"/>
android:text="@string/string_stop_patch"
app:onSafeClick="@{() -> viewmodel.onClickDeactivation()}" />
</LinearLayout>
</RelativeLayout>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<import type="info.nightscout.pump.medtrum.code.PatchStep" />
<variable
name="viewModel"
type="info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:fillViewport="true"
android:scrollbars="none">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.MedtrumActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/btn_negative"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/cancel"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@id/btn_positive"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.CANCEL)}" />
<Button
android:id="@+id/btn_positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:contentDescription="@string/string_start_prime"
android:text="@string/string_start_prime"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_negative"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.PRIME)}"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<import type="info.nightscout.pump.medtrum.code.PatchStep" />
<variable
name="viewModel"
type="info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:fillViewport="true"
android:scrollbars="none">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.MedtrumActivity">
<!-- TODO some stuff here that we are waiting :) -->
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>

View file

@ -11,10 +11,16 @@
<!-- overview fragment -->
<string name="medtrum_ble_status">BLE Status</string>
<string name="snInput_title">SN</string>
<string name="snInput_summary">Serial number pump base</string>
<string name="string_new_patch">Start new patch</string>
<string name="string_stop_patch">Stop patch</string>
<!-- wizard-->
<string name="string_change_patch">Discard/Change Patch</string> <!-- TODO check-->
<string name="string_start_prime">Start priming</string>
<!-- settings-->
<string name="snInput_title">SN</string>
<string name="snInput_summary">Serial number pump base</string>
</resources>