Merge remote-tracking branch 'Nightscout/dev' into Autotune/TuneWeekDaysClean

This commit is contained in:
Philoul 2023-06-03 23:12:01 +02:00
commit 0ce6d8d0d6
315 changed files with 11286 additions and 2626 deletions

View file

@ -5,7 +5,7 @@ version: 2.1
# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
orbs:
android: circleci/android@1.0.3
codecov: codecov/codecov@1.2.0
codecov: codecov/codecov@3.2.4
jobs:
# Below is the definition of your job to build and test your app, you can rename and customize it as you want.
@ -45,4 +45,4 @@ workflows:
# For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows
dotests:
jobs:
- build-and-test
- build-and-test

View file

@ -34,7 +34,7 @@ dependencies {
api 'org.slf4j:slf4j-api:1.7.36' // 2.0.x breaks logging. Code change needed
api 'com.github.tony19:logback-android:2.0.0'
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version"
api "org.apache.commons:commons-lang3:$commonslang3_version"
//RxBus

View file

@ -3,6 +3,7 @@ package info.nightscout.rx.weardata
import info.nightscout.rx.events.Event
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.joda.time.DateTime
import java.util.Objects
@Serializable
@ -90,6 +91,16 @@ sealed class EventData : Event() {
@Serializable
data class ActionQuickWizardPreCheck(val guid: String) : EventData()
@Serializable
data class ActionHeartRate(
val duration: Long,
val timestamp: Long,
val beatsPerMinute: Double,
val device: String): EventData() {
override fun toString() =
"HR ${beatsPerMinute.toInt()} at ${DateTime(timestamp)} for ${duration / 1000.0}sec $device"
}
@Serializable
data class ActionTempTargetPreCheck(
val command: TempTargetCommand,

View file

@ -16,8 +16,12 @@ fun PackageManager.safeGetInstalledPackages(flags: Int): List<PackageInfo> =
* Safe version of queryBroadcastReceivers depending on Android version running
*/
fun PackageManager.safeQueryBroadcastReceivers(intent: Intent, flags: Int): List<ResolveInfo> =
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) queryBroadcastReceivers(intent, PackageManager.ResolveInfoFlags.of(flags.toLong()))
else @Suppress("DEPRECATION") queryBroadcastReceivers(intent, flags)
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) queryBroadcastReceivers(intent, PackageManager.ResolveInfoFlags.of(flags.toLong()))
else @Suppress("DEPRECATION") queryBroadcastReceivers(intent, flags)
} catch (ignored: Exception) {
emptyList()
}
/**
* Safe version of getPackageInfo depending on Android version running

View file

@ -16,8 +16,6 @@ import androidx.annotation.RawRes
import androidx.annotation.StringRes
interface ResourceHelper {
fun updateContext(ctx: Context?)
fun gs(@StringRes id: Int): String
fun gs(@StringRes id: Int, vararg args: Any?): String
fun gq(@PluralsRes id: Int, quantity: Int, vararg args: Any?): String

View file

@ -111,7 +111,7 @@ android {
defaultConfig {
multiDexEnabled true
versionCode 1500
version "3.1.0.3-dev-h"
version "3.2.0-dev-j"
buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
@ -234,7 +234,7 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$dagger_version"
// MainApp
api "com.uber.rxdogtag2:rxdogtag:2.0.1"
api "com.uber.rxdogtag2:rxdogtag:2.0.2"
}

View file

@ -24,6 +24,7 @@ import android.widget.TextView
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat
import androidx.core.view.MenuCompat
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.tabs.TabLayoutMediator
@ -302,7 +303,7 @@ class MainActivity : DaggerAppCompatActivityWithResult() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menu.setGroupDividerEnabled(true)
MenuCompat.setGroupDividerEnabled(menu, true)
this.menu = menu
menuInflater.inflate(R.menu.menu_main, menu)
pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences)
@ -477,4 +478,4 @@ class MainActivity : DaggerAppCompatActivityWithResult() {
ToastUtils.okToast(context, context.getString(info.nightscout.core.ui.R.string.password_set))
}
}
}
}

View file

@ -11,9 +11,9 @@ import android.widget.TextView
import com.google.android.material.datepicker.MaterialDatePicker
import com.jjoe64.graphview.GraphView
import dagger.android.HasAndroidInjector
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.androidaps.databinding.ActivityHistorybrowseBinding
import info.nightscout.core.events.EventIobCalculationProgress
import info.nightscout.core.ui.activities.TranslatedDaggerAppCompatActivity
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.core.workflow.CalculationWorkflow
import info.nightscout.interfaces.Config
@ -42,7 +42,7 @@ import java.util.GregorianCalendar
import javax.inject.Inject
import kotlin.math.min
class HistoryBrowseActivity : DaggerAppCompatActivity() {
class HistoryBrowseActivity : TranslatedDaggerAppCompatActivity() {
@Inject lateinit var historyBrowserData: HistoryBrowserData
@Inject lateinit var injector: HasAndroidInjector
@ -327,6 +327,7 @@ class HistoryBrowseActivity : DaggerAppCompatActivity() {
var useRatioForScale = false
var useDSForScale = false
var useBGIForScale = false
var useHRForScale = false
when {
menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true
@ -335,6 +336,7 @@ class HistoryBrowseActivity : DaggerAppCompatActivity() {
menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] -> useHRForScale = true
}
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
@ -345,6 +347,7 @@ class HistoryBrowseActivity : DaggerAppCompatActivity() {
if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && config.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] && config.isDev()) secondGraphData.addHeartRate(useHRForScale, 1.0)
// set manual x bounds to have nice steps
secondGraphData.formatAxis(historyBrowserData.overviewData.fromTime, historyBrowserData.overviewData.endTime)

View file

@ -1,6 +1,5 @@
package info.nightscout.androidaps.activities
import android.content.Context
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@ -10,7 +9,6 @@ import androidx.preference.PreferenceScreen
import info.nightscout.androidaps.R
import info.nightscout.androidaps.databinding.ActivityPreferencesBinding
import info.nightscout.configuration.activities.DaggerAppCompatActivityWithResult
import info.nightscout.core.ui.locale.LocaleHelper
class PreferencesActivity : DaggerAppCompatActivityWithResult(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
@ -66,10 +64,6 @@ class PreferencesActivity : DaggerAppCompatActivityWithResult(), PreferenceFragm
return true
}
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.wrap(newBase))
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
android.R.id.home -> {

View file

@ -44,6 +44,8 @@ class ConfigImpl @Inject constructor(
engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile
unfinishedMode = unfinishedModeSemaphore.exists() && unfinishedModeSemaphore.isFile
devBranch = BuildConfig.VERSION.contains("-") || BuildConfig.VERSION.matches(Regex(".*[a-zA-Z]+.*"))
if (BuildConfig.VERSION.contains("-beta") || BuildConfig.VERSION.contains("-rc"))
devBranch = false
}
override fun isEngineeringModeOrRelease(): Boolean =

View file

@ -191,7 +191,7 @@ class KeepAliveWorker(
}
if (loop.isDisconnected) {
// do nothing if pump is disconnected
} else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) {
} else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile) || (runningProfile is ProfileSealed.EPS && runningProfile.value.originalEnd < dateUtil.now())) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) {
rxBus.send(EventProfileSwitchChanged())
} else if (isStatusOutdated && !pump.isBusy()) {
lastReadStatus = now

View file

@ -21,7 +21,7 @@
<string name="ns_announcements">Създаване на известия от NS съобщения</string>
<string name="ns_alarm_stale_data_value_label">Когато няма данни повече от [мин]</string>
<string name="ns_alarm_urgent_stale_data_value_label">Много стари данни при повече от [мин]</string>
<string name="sensitivity_warning">Когато включите Autosense feature трябва да въвеждате ВСИЧКИ въглехидрати. В противен случай те ще се изчисляват грешно като повишена чувствителност!!</string>
<string name="sensitivity_warning">Когато включите Autosense, трябва да въвеждате ВСИЧКИ въглехидрати. В противен случай те ще се изчисляват грешно, като повишена чувствителност!!</string>
<string name="notloadedplugins">Не всички профили са заредени!</string>
<string name="valuesnotstored">Стойностите не са запазени!</string>
<string name="invalid">НЕВАЛИДНО</string>

View file

@ -2,41 +2,40 @@
buildscript {
ext {
kotlin_version = '1.8.10'
core_version = '1.9.0'
kotlin_version = '1.8.21'
core_version = '1.10.1'
rxjava_version = '3.1.6'
rxandroid_version = '3.0.2'
rxkotlin_version = '3.0.1'
room_version = '2.5.0'
lifecycle_version = '2.5.1'
dagger_version = '2.45'
coroutines_version = '1.6.4'
activity_version = '1.6.1'
fragmentktx_version = '1.5.5'
room_version = '2.5.1'
lifecycle_version = '2.6.1'
dagger_version = '2.46.1'
coroutines_version = '1.7.1'
activity_version = '1.7.2'
fragmentktx_version = '1.5.7'
ormLite_version = '4.46'
gson_version = '2.10.1'
nav_version = '2.5.3'
appcompat_version = '1.6.1'
material_version = '1.8.0'
material_version = '1.9.0'
gridlayout_version = '1.0.0'
constraintlayout_version = '2.1.4'
preferencektx_version = '1.2.0'
commonslang3_version = '3.12.0'
commonscodec_version = '1.15'
jodatime_version = '2.10.14'
work_version = '2.8.0'
tink_version = '1.8.0'
work_version = '2.8.1'
tink_version = '1.9.0'
json_version = '20220320'
serialization_version = '1.4.1'
joda_version = '2.12.1.1'
joda_version = '2.12.5'
swipe_version = '1.1.0'
junit_version = '4.13.2'
junit_jupiter_version = '5.9.2'
junit_jupiter_version = '5.9.3'
mockito_version = '4.6.1'
dexmaker_version = '1.2'
retrofit2_version = '2.9.0'
okhttp3_version = '4.10.0'
okhttp3_version = '4.11.0'
byteBuddy_version = '1.12.8'
androidx_junit_version = '1.1.4'
@ -50,7 +49,7 @@ buildscript {
play_services_location_version = '21.0.1'
kotlinx_datetime_version = '0.4.0'
kotlinx_serialization_core_version = '1.4.1'
kotlinx_serialization_version = '1.5.1'
}
repositories {
google()
@ -58,9 +57,9 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" } // jacoco 0.2
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath 'com.google.gms:google-services:4.3.14'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.4'
classpath 'com.android.tools.build:gradle:7.4.2'
classpath 'com.google.gms:google-services:4.3.15'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@ -76,7 +75,7 @@ buildscript {
plugins {
// Test Gradle build, keep disabled under normal circumstances
// id "com.osacky.doctor" version "0.8.1"
id "org.jlleitschuh.gradle.ktlint" version "11.2.0"
id "org.jlleitschuh.gradle.ktlint" version "11.3.2"
id 'org.barfuin.gradle.jacocolog' version '3.1.0'
id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
}

View file

@ -150,4 +150,7 @@ interface OverviewData {
val dsMinScale: Scale
var dsMaxSeries: LineGraphSeries<ScaledDataPoint>
var dsMinSeries: LineGraphSeries<ScaledDataPoint>
}
var heartRateScale: Scale
var heartRateGraphSeries: LineGraphSeries<DataPointWithLabelInterface>
}

View file

@ -0,0 +1,24 @@
package info.nightscout.core.graph.data
import android.content.Context
import android.graphics.Paint
import info.nightscout.database.entities.HeartRate
import info.nightscout.shared.interfaces.ResourceHelper
class HeartRateDataPoint(
private val data: HeartRate,
private val rh: ResourceHelper,
) : DataPointWithLabelInterface {
override fun getX(): Double = (data.timestamp - data.duration).toDouble()
override fun getY(): Double = data.beatsPerMinute
override fun setY(y: Double) {}
override val label: String = ""
override val duration = data.duration
override val shape = PointsWithLabelGraphSeries.Shape.HEARTRATE
override val size = 1f
override val paintStyle: Paint.Style = Paint.Style.FILL
override fun color(context: Context?): Int = rh.gac(context, info.nightscout.core.ui.R.attr.heartRateColor)
}

View file

@ -54,7 +54,8 @@ public class PointsWithLabelGraphSeries<E extends DataPointWithLabelInterface> e
GENERAL_WITH_DURATION,
COB_FAIL_OVER,
IOB_PREDICTION,
BUCKETED_BG
BUCKETED_BG,
HEARTRATE,
}
/**
@ -324,6 +325,10 @@ public class PointsWithLabelGraphSeries<E extends DataPointWithLabelInterface> e
mPaint.setStrokeWidth(5);
canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint);
}
} else if (value.getShape() == Shape.HEARTRATE) {
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(0);
canvas.drawCircle(endX, endY, 1F, mPaint);
}
// set values above point
}

View file

@ -9,6 +9,8 @@ import info.nightscout.interfaces.userEntry.ValueWithUnitMapper
interface UserEntryLogger {
fun log(action: Action, source: Sources, note: String?, timestamp: Long, vararg listValues: ValueWithUnit?)
fun log(action: Action, source: Sources, note: String?, timestamp: Long, listValues: List<ValueWithUnit?>)
fun log(action: Action, source: Sources, note: String? = "", vararg listValues: ValueWithUnit?)
fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?)
fun log(action: Action, source: Sources, note: String? = "", listValues: List<ValueWithUnit?> = listOf())

View file

@ -133,6 +133,7 @@ open class Notification {
const val EOELOW_PATCH_ALERTS = 79
const val COMBO_PUMP_SUSPENDED = 80
const val COMBO_UNKNOWN_TBR = 81
const val BLUETOOTH_NOT_ENABLED = 82
const val USER_MESSAGE = 1000

View file

@ -43,7 +43,12 @@ interface StoreDataForDb {
val nsIdDeviceStatuses: MutableList<DeviceStatus>
val nsIdFoods: MutableList<Food>
val deleteTreatment: MutableList<String>
val deleteGlucoseValue: MutableList<String>
fun updateDeletedGlucoseValuesInDb()
fun storeTreatmentsToDb()
fun updateDeletedTreatmentsInDb()
fun storeGlucoseValuesToDb()
fun storeFoodsToDb()
fun scheduleNsIdUpdate()

View file

@ -15,7 +15,8 @@ interface OverviewMenus {
BGI,
SEN,
ACT,
DEVSLOPE
DEVSLOPE,
HR,
}
val setting: List<Array<Boolean>>
@ -23,4 +24,4 @@ interface OverviewMenus {
fun setupChartMenu(context: Context, chartButton: ImageButton)
fun enabledTypes(graph: Int): String
fun isEnabledIn(type: CharType): Int
}
}

View file

@ -25,7 +25,6 @@ interface CommandQueue {
fun extendedBolus(insulin: Double, durationInMinutes: Int, callback: Callback?): Boolean
fun cancelTempBasal(enforceNew: Boolean, callback: Callback?): Boolean
fun cancelExtended(callback: Callback?): Boolean
fun setProfile(profile: Profile, hasNsId: Boolean, callback: Callback?): Boolean
fun readStatus(reason: String, callback: Callback?): Boolean
fun statusInQueue(): Boolean
fun loadHistory(type: Byte, callback: Callback?): Boolean

View file

@ -23,7 +23,7 @@ dependencies {
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
api 'com.google.guava:guava:31.1-jre'
api 'com.google.guava:guava:32.0.0-jre'
api "androidx.activity:activity-ktx:$activity_version"
api "androidx.appcompat:appcompat:$appcompat_version"

View file

@ -34,5 +34,5 @@ dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version"
}

View file

@ -292,14 +292,11 @@ class NSAndroidClientImpl(
lastModified = response.body()?.lastModified
)
} else throw UnknownResponseNightscoutException()
} else if (response.code() in 400..499) {
return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = null,
errorResponse = response.errorBody()?.string() ?: response.message()
)
} else
throw UnsuccessfullNightscoutException(response.errorBody()?.string() ?: response.message())
} else return@callWrapper CreateUpdateResponse(
response = response.code(),
identifier = null,
errorResponse = response.errorBody()?.string() ?: response.message()
)
}
override suspend fun createTreatment(nsTreatment: NSTreatment): CreateUpdateResponse = callWrapper(dispatcher) {

View file

@ -45,7 +45,7 @@ internal fun RemoteTreatment.toTreatment(): NSTreatment? {
subject = this.subject,
isReadOnly = this.isReadOnly ?: false,
isValid = this.isValid ?: true,
eventType = this.eventType,
eventType = this.eventType ?: EventType.MEAL_BOLUS,
notes = this.notes,
pumpId = this.pumpId,
endId = this.endId,
@ -68,7 +68,7 @@ internal fun RemoteTreatment.toTreatment(): NSTreatment? {
subject = this.subject,
isReadOnly = this.isReadOnly ?: false,
isValid = this.isValid ?: true,
eventType = this.eventType,
eventType = this.eventType ?: EventType.CARBS_CORRECTION,
notes = this.notes,
pumpId = this.pumpId,
endId = this.endId,

View file

@ -29,7 +29,7 @@ internal data class RemoteTreatment(
@SerializedName("modifiedBy") val modifiedBy: String? = null, // string Name of the security subject (within Nightscout scope) which has patched or deleted the document for the last time. This field is automatically set by the server.
@SerializedName("isValid") val isValid: Boolean? = null, // boolean A flag set by the server only for deleted documents. This field appears only within history operation and for documents which were deleted by API v3 (and they always have a false value)
@SerializedName("isReadOnly") val isReadOnly: Boolean? = null, // boolean A flag set by client that locks the document from any changes. Every document marked with isReadOnly=true is forever immutable and cannot even be deleted.
@SerializedName("eventType") val eventType: EventType, // string "BG Check", "Snack Bolus", "Meal Bolus", "Correction Bolus", "Carb Correction", "Combo Bolus", "Announcement", "Note", "Question", "Exercise", "Site Change", "Sensor Start", "Sensor Change", "Pump Battery Change", "Insulin Change", "Temp Basal", "Profile Switch", "D.A.D. Alert", "Temporary Target", "OpenAPS Offline", "Bolus Wizard"
@SerializedName("eventType") val eventType: EventType?, // string "BG Check", "Snack Bolus", "Meal Bolus", "Correction Bolus", "Carb Correction", "Combo Bolus", "Announcement", "Note", "Question", "Exercise", "Site Change", "Sensor Start", "Sensor Change", "Pump Battery Change", "Insulin Change", "Temp Basal", "Profile Switch", "D.A.D. Alert", "Temporary Target", "OpenAPS Offline", "Bolus Wizard"
@SerializedName("glucose") val glucose: Double? = null, // double Current glucose
@SerializedName("glucoseType") val glucoseType: String? = null, // string example: "Sensor", "Finger", "Manual"
@SerializedName("units") val units: String? = null, // string The units for the glucose value, mg/dl or mmol/l. It is strongly recommended to fill in this field.

View file

@ -4,7 +4,7 @@ import android.content.Context
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.core.ui.locale.LocaleHelper
open class DialogAppCompatActivity : DaggerAppCompatActivity() {
open class TranslatedDaggerAppCompatActivity : DaggerAppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.wrap(newBase))
}

View file

@ -2,19 +2,21 @@ package info.nightscout.core.ui.locale
import android.content.Context
import android.content.ContextWrapper
import android.content.res.Configuration
import android.os.LocaleList
import androidx.preference.PreferenceManager
import info.nightscout.core.ui.R
import java.util.Locale
object LocaleHelper {
private fun selectedLanguage(context: Context): String =
PreferenceManager.getDefaultSharedPreferences(context).getString(context.getString(R.string.key_language), "default")
?: "default"
// injection not possible because of use in attachBaseContext
//SP.getString(R.string.key_language, Locale.getDefault().language)
private fun currentLocale(context: Context): Locale {
fun currentLocale(context: Context): Locale {
val language = selectedLanguage(context)
if (language == "default") return Locale.getDefault()
@ -34,18 +36,13 @@ object LocaleHelper {
val locale = currentLocale(context)
Locale.setDefault(locale)
val resources = context.resources
val configuration = resources.configuration
context.createConfigurationContext(configuration)
configuration.setLocale(locale)
}
fun wrap(ctx: Context): Context {
// no action for system default language
if (selectedLanguage(ctx) == "default") return ctx
val res = ctx.resources
val configuration = res.configuration
val configuration = Configuration()
val newLocale = currentLocale(ctx)
configuration.setLocale(newLocale)
val localeList = LocaleList(newLocale)

View file

@ -164,7 +164,6 @@
<string name="isf_short">Чувств</string>
<string name="canceling_tbr_failed">Отмяната на временния базал е неуспешно</string>
<string name="canceling_eb_failed">Неуспешно спиране на удължен болус</string>
<string name="virtualpump_uploadstatus_title">Качване на статус в NS или Tidepool</string>
<string name="suspendloop_label">Изключен/забранен цикъл</string>
<string name="iob_label">Активен Инсулин (IOB)</string>
<!-- Protection-->
@ -298,7 +297,6 @@
<string name="uel_site_change">СМЯНА КАНЮЛА</string>
<string name="uel_reservoir_change">СМЯНА НА РЕЗЕРВОАР</string>
<string name="uel_calibration">КАЛИБРАЦИЯ</string>
<string name="uel_prime_bolus">БОЛУС ПЪЛНЕНЕНЕ</string>
<string name="uel_treatment">ЛЕЧЕНИЕ</string>
<string name="uel_careportal_ns_refresh">ОБНОВИ ПОРТАЛА ЗА ЛЕЧЕНИЯ NS</string>
<string name="uel_profile_switch_ns_refresh">ОБНОВИ СМЯНАТА НА ПРОФИЛ NS</string>
@ -463,10 +461,10 @@
<string name="wizard_carbs_constraint">Нарушение на въхлехидратните ограничения!</string>
<string name="wizard_explain_calc">Изчисляване (въглехидратно число: %1$.1f, инсулинова чувствителност: %2$.1f)</string>
<string name="wizard_explain_carbs">Въглехидрати: %1$.2fЕ</string>
<string name="wizard_explain_cob">Остатъчни въглехидрати: %1$.0fg %2$.2fU</string>
<string name="wizard_explain_bg">Кръвна захар: %1$.2fU</string>
<string name="wizard_explain_cob">Активни въглехидрати: %1$.0fгр %2$.2fЕ</string>
<string name="wizard_explain_bg">КЗ: %1$.2fЕ</string>
<string name="wizard_explain_iob">Остатъчен инсулин: %1$.2fU</string>
<string name="wizard_explain_superbolus">Суперболус: %1$.2fU</string>
<string name="wizard_explain_superbolus">Суперболус: %1$.2fЕ</string>
<string name="wizard_explain_trend">15\' тенденция: %1$.2fЕ</string>
<string name="wizard_explain_percent">Проценти: %1$.2fЕ x %2$d%% ≈ %3$.2fЕ</string>
<string name="wizard_constraint_bolus_size">Нарушение в ограниичението на инсулин!\nНе може да бъде доставен %1$.2fЕ</string>
@ -572,7 +570,7 @@
<item quantity="other">%1$d минути</item>
</plurals>
<!-- Maintenance-->
<string name="cleanup_db_confirm">Искате ли да почистите базата данни\nТова ще премахне проследените промени и историята на данните, по-стари от 3 месеца.</string>
<string name="cleanup_db_confirm">Искате ли да изтриете базата данни\nТова ще премахне проследените промени и историята на данните, по-стари от 3 месеца.</string>
<string name="cleanup_db_confirm_sync">Искате ли да изтриете базата данни?\nТова ще премахне проследените промени и историята на данните, по-стари от 3 месеца.\nИзвършването му ще ускори драстично пълната синхронизация.</string>
<string name="cleared_entries">Изчистени записи</string>
</resources>

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Rušení dočasného bazálu selhalo</string>
<string name="canceling_eb_failed">Zastavení prodlouženého bolusu selhalo</string>
<string name="virtualpump_uploadstatus_title">Nahrát stav do NS nebo Tidepoolu</string>
<string name="virtualpump_uploadstatus_title">Nahrávat stav pumpy do NS nebo Tidepoolu</string>
<string name="suspendloop_label">Zakázaná/pozastavená smyčka</string>
<string name="iob_label">Aktivní inzulín (IOB)</string>
<!-- Protection-->

View file

@ -164,7 +164,6 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Abbruch der temporären Basalrate fehlgeschlagen</string>
<string name="canceling_eb_failed">Der Abbruch des erweiterten Bolus ist fehlgeschlagen</string>
<string name="virtualpump_uploadstatus_title">Status zu NS oder Tidepool hochladen</string>
<string name="suspendloop_label">Deaktiviere/Pausiere den Loop</string>
<string name="iob_label">Aktives Insulin (IOB)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Error cancelando la basal temporal</string>
<string name="canceling_eb_failed">Error cancelando el bolo extendido</string>
<string name="virtualpump_uploadstatus_title">Subir estado a NS o Tidepool</string>
<string name="virtualpump_uploadstatus_title">Subir estado de la bomba a NS o Tidepool</string>
<string name="suspendloop_label">Desactiva/suspende el bucle</string>
<string name="iob_label">Insulina a bordo (IOB)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">SI</string>
<string name="canceling_tbr_failed">Echec de l\'annulation du basal temporaire</string>
<string name="canceling_eb_failed">Échec de l\'annulation du Bolus étendu</string>
<string name="virtualpump_uploadstatus_title">Transférer le statut vers NS ou Tidepool</string>
<string name="virtualpump_uploadstatus_title">Télécharger l\'état de la pompe sur NS ou Tidepool</string>
<string name="suspendloop_label">Boucle désactivée/suspendue</string>
<string name="iob_label">Insuline Active (IA)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Basale temporanea: cancellazione fallita</string>
<string name="canceling_eb_failed">Bolo esteso: cancellazione fallita</string>
<string name="virtualpump_uploadstatus_title">Carica stato su NS o Tidepool</string>
<string name="virtualpump_uploadstatus_title">Carica stato micro su NS o Tidepool</string>
<string name="suspendloop_label">Loop disabilitato/sospeso</string>
<string name="iob_label">Insulina attiva (IOB)</string>
<!-- Protection-->
@ -449,6 +449,7 @@
<string name="info">INFO</string>
<!-- BolusWizard -->
<string name="bolus_advisor">Consiglio bolo</string>
<string name="bolus_advisor_message">Hai una glicemia alta. Invece di mangiare ora, si consiglia di attendere una glicemia migliore. Vuoi fare adesso un bolo di correzione ed essere ricordato quando è il momento di mangiare? In questo caso non verranno registrati carboidrati e dovrai usare di nuovo il calcolatore quando ti verrà mostrato il promemoria.</string>
<string name="cobvsiob">COB vs IOB</string>
<string name="slowabsorptiondetected"><![CDATA[<font color=\'%1$s\'>!!!!! Rilevato assorbimento lento dei carboidrati: %2$d%% del tempo. Ricontrolla il tuo calcolo. COB potrebbero essere sovrastimati e potrebbe essere somministrata più insulina !!!!!</font>]]></string>
<string name="partialboluswizard">Eroga parte del risultato del calcolatore [%]</string>

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">ביטול בזאלי זמני נכשל</string>
<string name="canceling_eb_failed">ביטול בולוס ממושך נכשל</string>
<string name="virtualpump_uploadstatus_title">סטטוס העלאה לנייטסקאוט או ל-Tidepool</string>
<string name="virtualpump_uploadstatus_title">העלה את סטטוס המשאבה לנייטסקאוט או ל-Tidepool</string>
<string name="suspendloop_label">השבתת \\ השהיית לולאה</string>
<string name="iob_label">אינסולין פעיל בגוף (IOB)</string>
<!-- Protection-->

View file

@ -23,7 +23,7 @@
<string name="mgdl">mg/dl</string>
<string name="mmol">mmol/l</string>
<string name="save">Išsaugoti</string>
<string name="snooze">Snausti</string>
<string name="snooze">Nutildyti</string>
<string name="virtual_pump">Virtuali pompa</string>
<string name="constraints">Apribojimai</string>
<string name="superbolus">Superbolus</string>
@ -82,7 +82,7 @@
<string name="bluetooth">Bluetooth</string>
<string name="btwatchdog_title">BT Watchdog</string>
<string name="btwatchdog_summary">Vienai sekundei išjungia telefono bluetooth, jei ryšys su pompa nutrūksta. Gali būti veiksminga kai kuriems telefonų modeliams, turintiems BT problemų.</string>
<string name="virtualpump_resultok">Gerai</string>
<string name="virtualpump_resultok">OK</string>
<string name="pump_time_updated">Pompos laikas pakeistas</string>
<string name="exit">Išeiti</string>
<string name="removerecord">Ištrinti įrašą</string>
@ -164,7 +164,7 @@
<string name="isf_short">JIF</string>
<string name="canceling_tbr_failed">Laikinos bazės atšaukti nepavyko</string>
<string name="canceling_eb_failed">Ištęsto boluso atšaukti nepavyko</string>
<string name="virtualpump_uploadstatus_title">Įkelti statusą į NS arba Tidepool</string>
<string name="virtualpump_uploadstatus_title">Įkelti pompos statusą į NS arba Tidepool</string>
<string name="suspendloop_label">Ciklas išjungtas/sustabdytas</string>
<string name="iob_label">Aktyvus insulinas organizme (AIO)</string>
<!-- Protection-->
@ -232,7 +232,7 @@
<string name="hypo">Hipo</string>
<string name="activity">Aktyvumas</string>
<string name="wear">Išmanieji laikrodžiai</string>
<string name="automation">Automatiškai</string>
<string name="automation">Automatizacija</string>
<string name="custom">Pasirinktinis</string>
<string name="loop">Ciklas</string>
<string name="ns">NS</string>
@ -288,7 +288,7 @@
<string name="uel_resume">ATNAUJINTI</string>
<string name="uel_suspend">SUSTABDYTI</string>
<string name="uel_hw_pump_allowed">HW POMPA LEIDŽIAMA</string>
<string name="uel_clear_pairing_keys">IŠVALYTI PORAVIMO KODUS</string>
<string name="uel_clear_pairing_keys">IŠVALYTI SUSIEJIMO KODUS</string>
<string name="uel_accepts_temp_basal">PRIIMTI LAIKINĄ BAZĘ</string>
<string name="uel_cancel_temp_basal">ATŠAUKTI LAIKINĄ BAZĘ</string>
<string name="uel_cancel_bolus">ATŠAUKTI BOLUSĄ</string>
@ -387,7 +387,7 @@
<string name="temp_basal_percent">LAIKINA BAZĖ %1$d%% %2$d min</string>
<string name="insight_set_tbr_over_notification">INSIGHT NUSTATYTI LAIKINĄ BAZĘ PRANEŠIMU</string>
<string name="read_status" comment="10 characters max for READSTATUS translation">STATUSAS %1$s</string>
<string name="keepalive_status_outdated" comment="26 characters max for translation">Tęsti. Būklė per sena.</string>
<string name="keepalive_status_outdated" comment="26 characters max for translation">Tęsti. Statusas senas.</string>
<string name="keepalive_basal_outdated" comment="26 characters max for translation">Tęsti. Bazė per sena.</string>
<string name="sms" comment="26 characters max for translation">SMS</string>
<string name="formatPercent">%1$.0f%%</string>
@ -513,7 +513,7 @@
<string name="connection_error">Pompos prisijungimo klaida</string>
<string name="reading_pump_history">Skaitoma pompos istorija</string>
<string name="password_cleared">Slaptažodis išvalytas!</string>
<string name="pairing">Sujungiama</string>
<string name="pairing">Susiejimas</string>
<string name="initializing">Inicijuojama ...</string>
<!-- Constraints-->
<string name="limitingbasalratio">Ribojamas maksimalus bazės dydis%1$.2f vv/val dėl %2$s</string>
@ -525,7 +525,7 @@
<!-- Dialogs-->
<string name="confirmation">Patvirtinimas</string>
<string name="message">Pranešimas</string>
<string name="ok">Gerai</string>
<string name="ok">OK</string>
<string name="cancel">Atšaukti</string>
<string name="dismiss">ATMESTI</string>
<string name="yes">Taip</string>
@ -544,13 +544,13 @@
<string name="need_connect_permission">Programai reikalinga Bluetooth prieigos teisė</string>
<!-- Combo-->
<string name="user_request" comment="26 characters max for translation">Vartotojo užklausa</string>
<string name="pump_paired" comment="26 characters max for translation">Pompa suporuota</string>
<string name="pump_paired" comment="26 characters max for translation">Pompa susieta</string>
<!-- BlePreCheck-->
<string name="ble_not_supported">Bluetooth Low Energy nepalaikoma.</string>
<string name="ble_not_supported_or_not_paired">Nepalaikomas Bluetooth Low Energy arba įrenginys nesuporuotas.</string>
<string name="ble_not_supported_or_not_paired">Nepalaikomas BLE arba įrenginys nesusietas.</string>
<string name="ble_not_enabled">Bluetooth neįjungta.</string>
<string name="location_not_found_title">Vietovės nustatymas neįjungtas</string>
<string name="location_not_found_message">Vietos nustatymo paslauga turi būti įjungta, kad Bluetooth aptikimas veiktų naujesniuose įrenginiuose. AAPS neseka Jūsų lokacijos, o vietos nustatymo paslauga gali būti išjungta po sėkmingo įrenginių suporavimo.</string>
<string name="location_not_found_message">Vietos nustatymo paslauga turi būti įjungta, kad Bluetooth aptikimas veiktų naujesniuose įrenginiuose. AAPS neseka Jūsų lokacijos, o vietos nustatymo paslauga gali būti išjungta po sėkmingo įrenginių susiejimo.</string>
<!-- Preferences -->
<string name="nav_plugin_preferences">Įskiepių nustatymai</string>
<!-- SmsCommunicator -->

View file

@ -216,6 +216,7 @@
<item name="inRangeBackground">@color/inRangeBackground</item>
<item name="devSlopePosColor">@color/devSlopePos</item>
<item name="devSlopeNegColor">@color/devSlopeNeg</item>
<item name="heartRateColor">@color/heartRate</item>
<item name="deviationGreyColor">@color/deviationGrey</item>
<item name="deviationBlackColor">@color/deviationBlack</item>
<item name="deviationGreenColor">@color/deviationGreen</item>

View file

@ -164,7 +164,6 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Annuleren van tijdelijke basaal mislukt</string>
<string name="canceling_eb_failed">Annuleren van vertraagde bolus is mislukt</string>
<string name="virtualpump_uploadstatus_title">Upload status naar NS of Tidepool</string>
<string name="suspendloop_label">Uitgeschakelde/onderbroken loop</string>
<string name="iob_label">Insuline aan boord (IOB)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Kunne ikke avbryte midlertidig basal</string>
<string name="canceling_eb_failed">Kunne ikke avbryte forlenget bolus</string>
<string name="virtualpump_uploadstatus_title">Last opp status til NS eller Tidepool</string>
<string name="virtualpump_uploadstatus_title">Last opp pumpestatus til NS eller Tidepool</string>
<string name="suspendloop_label">Deaktivert/pauset loop</string>
<string name="iob_label">Aktivt insulin (IOB)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Anulowanie bazy tymczasowej nie powiodło się</string>
<string name="canceling_eb_failed">Anulowanie przedłużonego bolusa nie powiodło się</string>
<string name="virtualpump_uploadstatus_title">Prześlij status do NS lub Tidepool</string>
<string name="virtualpump_uploadstatus_title">Prześlij status pompy do NS lub Tidepool</string>
<string name="suspendloop_label">Wyłączona/zawieszona pętla</string>
<string name="iob_label">Aktywna insulina (IOB)</string>
<!-- Protection-->
@ -532,7 +532,7 @@
<string name="no">Nie</string>
<string name="close">Zamknij</string>
<!-- TwoMessagesDialog -->
<string name="password_preferences_decrypt_prompt">Zostaniesz poproszony o hasło główne, które jest potrzebne do odszyfrowania zaimportowanych preferencji.</string>
<string name="password_preferences_decrypt_prompt">Zostaniesz poproszony o hasło główne, które jest potrzebne do odszyfrowania zaimportowanych ustawień.</string>
<!-- NumberPicker -->
<string name="a11y_min_button_description">zmniejszenie %1$s o %2$s</string>
<string name="a11y_plus_button_description">zwiększenie %1$s o %2$s</string>

View file

@ -164,7 +164,6 @@
<string name="isf_short">FSI</string>
<string name="canceling_tbr_failed">Cancelamento do basal temporário falhou</string>
<string name="canceling_eb_failed">Falhou o cancelamento do bolus extendido</string>
<string name="virtualpump_uploadstatus_title">Carregar status para NS ou Tidepool</string>
<string name="suspendloop_label">Loop Desativado/Suspenso</string>
<string name="iob_label">Insulina ativa (IA)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF (чувствительность к инсулину)</string>
<string name="canceling_tbr_failed">Отмена врем базала не состоялась</string>
<string name="canceling_eb_failed">Сбой отмены пролонгированного болюса</string>
<string name="virtualpump_uploadstatus_title">Статус dsuheprb в NS или Tidepool</string>
<string name="virtualpump_uploadstatus_title">Передавать статус помпы в NS или Tidepool</string>
<string name="suspendloop_label">Отключенный/приостановленный цикл</string>
<string name="iob_label">Активный инсулин (IOB)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Zrušenie dočasného bazálu zlyhalo</string>
<string name="canceling_eb_failed">Zastavenie predĺženého bolusu zlyhalo</string>
<string name="virtualpump_uploadstatus_title">Nahrať stav do NS, alebo Tidepool</string>
<string name="virtualpump_uploadstatus_title">Nahrávať stav pumpy do NS, alebo Tidepoolu</string>
<string name="suspendloop_label">Deaktivovaný/pozastavený uzavretý okruh</string>
<string name="iob_label">Aktívny inzulín (IOB)</string>
<!-- Protection-->

View file

@ -164,7 +164,7 @@
<string name="isf_short">IDF İnsülin Duyarlılık Faktörü</string>
<string name="canceling_tbr_failed">Geçici bazal iptali başarısız oldu</string>
<string name="canceling_eb_failed">Yayma bolusun iptal edilmesi başarısız oldu</string>
<string name="virtualpump_uploadstatus_title">Durumu NS\'a veya Tidepool\'a yükleyin</string>
<string name="virtualpump_uploadstatus_title">Pompa durumunu NS veya Tidepool\'a yükleyin</string>
<string name="suspendloop_label">Döngüyü Devre Dışı bırakma/Askıya alma</string>
<string name="iob_label">Aktif İnsülin (AİNS)</string>
<!-- Protection-->

View file

@ -181,6 +181,7 @@
<attr name="bolusDataPointColor" format="reference|color" />
<attr name="profileSwitchColor" format="reference|color" />
<attr name="originalBgValueColor" format="reference|color" />
<attr name="heartRateColor" format="reference|color" />
<attr name="therapyEvent_NS_MBG" format="reference|color" />
<attr name="therapyEvent_FINGER_STICK_BG_VALUE" format="reference|color" />
<attr name="therapyEvent_EXERCISE" format="reference|color" />
@ -221,4 +222,4 @@
<attr name="crossTargetColor" format="reference|color" />
<!---Custom button -->
<attr name="customBtnStyle" format="reference"/>
</resources>
</resources>

View file

@ -153,6 +153,7 @@
<color name="bgi">#00EEEE</color>
<color name="devSlopePos">#FFFFFF00</color>
<color name="devSlopeNeg">#FFFF00FF</color>
<color name="heartRate">#FFFFFF66</color>
<color name="actionsConfirm">#F6CE22</color>
<color name="deviations">#FF0000</color>
<color name="cobAlert">#7484E2</color>

View file

@ -165,7 +165,7 @@
<string name="isf_short">ISF</string>
<string name="canceling_tbr_failed">Canceling of temporary basal failed</string>
<string name="canceling_eb_failed">Canceling of extended bolus failed</string>
<string name="virtualpump_uploadstatus_title">Upload status to NS or Tidepool</string>
<string name="virtualpump_uploadstatus_title">Upload pump status to NS or Tidepool</string>
<string name="suspendloop_label">Disabled/Suspended loop</string>
<string name="iob_label">Insulin on Board (IOB)</string>

View file

@ -219,6 +219,7 @@
<item name="inRangeBackground">@color/inRangeBackground</item>
<item name="devSlopePosColor">@color/devSlopePos</item>
<item name="devSlopeNegColor">@color/devSlopeNeg</item>
<item name="heartRateColor">@color/heartRate</item>
<item name="deviationGreyColor">@color/deviationGrey</item>
<item name="deviationBlackColor">@color/deviationBlack</item>
<item name="deviationGreenColor">@color/deviationGreen</item>

View file

@ -22,7 +22,7 @@ dependencies {
implementation project(':app-wear-shared:shared')
//Firebase
api platform('com.google.firebase:firebase-bom:31.2.2')
api platform('com.google.firebase:firebase-bom:32.1.0')
api "com.google.firebase:firebase-analytics-ktx"
api "com.google.firebase:firebase-crashlytics-ktx"
// StatsActivity not in use now

View file

@ -5,6 +5,7 @@ import android.view.ActionMode
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.view.MenuCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import info.nightscout.shared.interfaces.ResourceHelper
@ -68,7 +69,7 @@ class ActionModeHelper<T>(val rh: ResourceHelper, val activity: FragmentActivity
} else if (fragment?.isResumed == true) {
menu.add(Menu.FIRST, R.id.nav_remove_items, 0, rh.gs(R.string.remove_items)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.add(Menu.FIRST, R.id.nav_sort_items, 0, rh.gs(R.string.sort_items)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
MenuCompat.setGroupDividerEnabled(menu, true)
}
}

View file

@ -7,6 +7,7 @@ plugins {
}
apply from: "${project.rootDir}/core/main/android_dependencies.gradle"
apply from: "${project.rootDir}/core/main/test_dependencies.gradle"
android {
@ -30,4 +31,4 @@ dependencies {
allOpen {
// allows mocking for classes w/o directly opening them for release builds
annotation 'info.nightscout.database.annotations.DbOpenForTesting'
}
}

View file

@ -0,0 +1,44 @@
package info.nightscout.database.entities
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import info.nightscout.database.entities.embedments.InterfaceIDs
import info.nightscout.database.entities.interfaces.DBEntryWithTimeAndDuration
import info.nightscout.database.entities.interfaces.TraceableDBEntry
import java.util.*
/** Heart rate values measured by a user smart watch or the like. */
@Entity(
tableName = TABLE_HEART_RATE,
indices = [Index("id"), Index("timestamp")]
)
data class HeartRate(
@PrimaryKey(autoGenerate = true)
override var id: Long = 0,
/** Duration milliseconds */
override var duration: Long,
/** Milliseconds since the epoch. End of the sampling period, i.e. the value is
* sampled from timestamp-duration to timestamp. */
override var timestamp: Long,
var beatsPerMinute: Double,
/** Source device that measured the heart rate. */
var device: String,
override var utcOffset: Long = TimeZone.getDefault().getOffset(timestamp).toLong(),
override var version: Int = 0,
override var dateCreated: Long = -1,
override var isValid: Boolean = true,
override var referenceId: Long? = null,
@Embedded
override var interfaceIDs_backing: InterfaceIDs? = null
) : TraceableDBEntry, DBEntryWithTimeAndDuration {
fun contentEqualsTo(other: HeartRate): Boolean {
return this === other || (
duration == other.duration &&
timestamp == other.timestamp &&
beatsPerMinute == other.beatsPerMinute &&
isValid == other.isValid)
}
}

View file

@ -8,6 +8,7 @@ const val TABLE_CARBS = "carbs"
const val TABLE_DEVICE_STATUS = "deviceStatus"
const val TABLE_EFFECTIVE_PROFILE_SWITCHES = "effectiveProfileSwitches"
const val TABLE_EXTENDED_BOLUSES = "extendedBoluses"
const val TABLE_HEART_RATE = "heartRate"
const val TABLE_GLUCOSE_VALUES = "glucoseValues"
const val TABLE_FOODS = "foods"
const val TABLE_MULTIWAVE_BOLUS_LINKS = "multiwaveBolusLinks"

View file

@ -8,6 +8,7 @@ import info.nightscout.database.entities.Carbs
import info.nightscout.database.entities.EffectiveProfileSwitch
import info.nightscout.database.entities.ExtendedBolus
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.entities.HeartRate
import info.nightscout.database.entities.MultiwaveBolusLink
import info.nightscout.database.entities.OfflineEvent
import info.nightscout.database.entities.PreferenceChange
@ -35,5 +36,6 @@ data class NewEntries(
val temporaryTarget: List<TemporaryTarget>,
val therapyEvents: List<TherapyEvent>,
val totalDailyDoses: List<TotalDailyDose>,
val versionChanges: List<VersionChange>
)
val versionChanges: List<VersionChange>,
val heartRates: List<HeartRate>,
)

View file

@ -0,0 +1,36 @@
package info.nightscout.database.entities
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.jupiter.api.Test
class HeartRateTest {
@Test
fun contentEqualsTo_equals() {
val hr1 = createHeartRate()
assertTrue(hr1.contentEqualsTo(hr1))
assertTrue(hr1.contentEqualsTo(hr1.copy()))
assertTrue(hr1.contentEqualsTo(hr1.copy (id = 2, version = 2, dateCreated = 1L, referenceId = 4L)))
}
@Test
fun contentEqualsTo_notEquals() {
val hr1 = createHeartRate()
assertFalse(hr1.contentEqualsTo(hr1.copy(duration = 60_001L)))
assertFalse(hr1.contentEqualsTo(hr1.copy(timestamp = 2L)))
assertFalse(hr1.contentEqualsTo(hr1.copy(duration = 60_001L)))
assertFalse(hr1.contentEqualsTo(hr1.copy(beatsPerMinute = 100.0)))
assertFalse(hr1.contentEqualsTo(hr1.copy(isValid = false)))
}
companion object {
fun createHeartRate(timestamp: Long? = null, beatsPerMinute: Double = 80.0) =
HeartRate(
timestamp = timestamp ?: System.currentTimeMillis(),
duration = 60_0000L,
beatsPerMinute = beatsPerMinute,
device = "T",
)
}
}

View file

@ -8,6 +8,8 @@ plugins {
apply from: "${project.rootDir}/core/main/android_dependencies.gradle"
apply from: "${project.rootDir}/core/main/android_module_dependencies.gradle"
apply from: "${project.rootDir}/core/main/test_dependencies.gradle"
apply from: "${project.rootDir}/core/main/jacoco_global.gradle"
android {
@ -20,6 +22,9 @@ android {
}
}
}
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas")
}
}
dependencies {
@ -44,9 +49,11 @@ dependencies {
api "com.google.dagger:dagger-android:$dagger_version"
api "com.google.dagger:dagger-android-support:$dagger_version"
androidTestImplementation "androidx.room:room-testing:$room_version"
}
allOpen {
// allows mocking for classes w/o directly opening them for release builds
annotation 'info.nightscout.database.annotations.DbOpenForTesting'
}
}

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 23,
"identityHash": "173734db5f4f35f6295ed953d8124794",
"identityHash": "a3ee37800b6cda170d0ea64799ed7876",
"entities": [
{
"tableName": "apsResults",
@ -3689,12 +3689,153 @@
]
}
]
},
{
"tableName": "heartRate",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `duration` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `beatsPerMinute` REAL NOT NULL, `device` TEXT NOT NULL, `utcOffset` INTEGER NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "beatsPerMinute",
"columnName": "beatsPerMinute",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "device",
"columnName": "device",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "utcOffset",
"columnName": "utcOffset",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "version",
"columnName": "version",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dateCreated",
"columnName": "dateCreated",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isValid",
"columnName": "isValid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "referenceId",
"columnName": "referenceId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.nightscoutSystemId",
"columnName": "nightscoutSystemId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.nightscoutId",
"columnName": "nightscoutId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.pumpType",
"columnName": "pumpType",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.pumpSerial",
"columnName": "pumpSerial",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.temporaryId",
"columnName": "temporaryId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.pumpId",
"columnName": "pumpId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.startId",
"columnName": "startId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "interfaceIDs_backing.endId",
"columnName": "endId",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_heartRate_id",
"unique": false,
"columnNames": [
"id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_id` ON `${TABLE_NAME}` (`id`)"
},
{
"name": "index_heartRate_timestamp",
"unique": false,
"columnNames": [
"timestamp"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_heartRate_timestamp` ON `${TABLE_NAME}` (`timestamp`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '173734db5f4f35f6295ed953d8124794')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a3ee37800b6cda170d0ea64799ed7876')"
]
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,126 @@
package info.nightscout.database.impl
import android.content.Context
import androidx.room.Room
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import info.nightscout.database.entities.HeartRate
import info.nightscout.database.entities.TABLE_HEART_RATE
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
internal class HeartRateDaoTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
private fun createDatabase() =
Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
private fun getDbObjects(supportDb: SupportSQLiteDatabase, type: String): Set<String> {
val names = mutableSetOf<String>()
supportDb.query("SELECT name FROM sqlite_master WHERE type = '$type'").use { c ->
while (c.moveToNext()) names.add(c.getString(0))
}
return names
}
private fun getTableNames(db: SupportSQLiteDatabase) = getDbObjects(db, "table")
private fun getIndexNames(db: SupportSQLiteDatabase) = getDbObjects(db, "index")
private fun insertAndFind(database: AppDatabase) {
val hr1 = createHeartRate()
val id = database.heartRateDao.insert(hr1)
val hr2 = database.heartRateDao.findById(id)
assertTrue(hr1.contentEqualsTo(hr2!!))
}
@Test
fun new_insertAndFind() {
createDatabase().use { db -> insertAndFind(db) }
}
@Test
fun migrate_createsTableAndIndices() {
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java
)
val startVersion = 22
val supportDb = helper.createDatabase(TEST_DB_NAME, startVersion)
assertFalse(getTableNames(supportDb).contains(TABLE_HEART_RATE))
DatabaseModule().migrations.filter { m -> m.startVersion >= startVersion }.forEach { m -> m.migrate(supportDb) }
assertTrue(getTableNames(supportDb).contains(TABLE_HEART_RATE))
assertTrue(getIndexNames(supportDb).contains("index_heartRate_id"))
assertTrue(getIndexNames(supportDb).contains("index_heartRate_timestamp"))
}
@Test
fun migrate_insertAndFind() {
val helper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java
)
// Create the database for version 22 (that's missing the heartRate table).
// helper.createDatabase removes the db file if it already exists.
val supportDb = helper.createDatabase(TEST_DB_NAME, 22)
assertFalse(getTableNames(supportDb).contains(TABLE_HEART_RATE))
// Room.databaseBuilder will use the previously created db file that has version 22.
Room.databaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java, TEST_DB_NAME)
.addMigrations(*DatabaseModule().migrations)
.build().use { db -> insertAndFind(db) }
}
@Test
fun getFromTime() {
createDatabase().use { db ->
val dao = db.heartRateDao
val timestamp = System.currentTimeMillis()
val hr1 = createHeartRate(timestamp = timestamp, beatsPerMinute = 80.0)
val hr2 = createHeartRate(timestamp = timestamp + 1, beatsPerMinute = 150.0)
dao.insertNewEntry(hr1)
dao.insertNewEntry(hr2)
assertEquals(listOf(hr1, hr2), dao.getFromTime(timestamp))
assertEquals(listOf(hr2), dao.getFromTime(timestamp + 1))
assertTrue(dao.getFromTime(timestamp + 2).isEmpty())
}
}
@Test
fun getFromTimeToTime() {
createDatabase().use { db ->
val dao = db.heartRateDao
val timestamp = System.currentTimeMillis()
val hr1 = createHeartRate(timestamp = timestamp, beatsPerMinute = 80.0)
val hr2 = createHeartRate(timestamp = timestamp + 1, beatsPerMinute = 150.0)
val hr3 = createHeartRate(timestamp = timestamp + 2, beatsPerMinute = 160.0)
dao.insertNewEntry(hr1)
dao.insertNewEntry(hr2)
dao.insertNewEntry(hr3)
assertEquals(listOf(hr1, hr2, hr3), dao.getFromTimeToTime(timestamp, timestamp + 2))
assertEquals(listOf(hr1, hr2), dao.getFromTimeToTime(timestamp, timestamp + 1))
assertEquals(listOf(hr2), dao.getFromTimeToTime(timestamp + 1, timestamp + 1))
assertTrue(dao.getFromTimeToTime(timestamp + 3, timestamp + 10).isEmpty())
}
}
companion object {
private const val TEST_DB_NAME = "testDatabase"
fun createHeartRate(timestamp: Long? = null, beatsPerMinute: Double = 80.0) =
HeartRate(
timestamp = timestamp ?: System.currentTimeMillis(),
duration = 60_0000L,
beatsPerMinute = beatsPerMinute,
device = "T",
)
}
}

View file

@ -0,0 +1,57 @@
package info.nightscout.database.impl.transactions
import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import info.nightscout.database.impl.AppDatabase
import info.nightscout.database.impl.AppRepository
import info.nightscout.database.impl.HeartRateDaoTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class InsertOrUpdateHeartRateTransactionTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var db: AppDatabase
private lateinit var repo: AppRepository
@Before
fun setupUp() {
db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()
repo = AppRepository(db)
}
@After
fun shutdown() {
db.close()
}
@Test
fun createNewEntry() {
val hr1 = HeartRateDaoTest.createHeartRate()
val result = repo.runTransactionForResult(InsertOrUpdateHeartRateTransaction(hr1)).blockingGet()
assertEquals(listOf(hr1), result.inserted)
assertTrue(result.updated.isEmpty())
}
@Test
fun updateEntry() {
val hr1 = HeartRateDaoTest.createHeartRate()
val id = db.heartRateDao.insertNewEntry(hr1)
assertNotEquals(0, id)
val hr2 = hr1.copy(id = id, beatsPerMinute = 181.0)
val result = repo.runTransactionForResult(InsertOrUpdateHeartRateTransaction(hr2)).blockingGet()
assertEquals(listOf(hr2), result.updated)
assertTrue(result.inserted.isEmpty())
val hr3 = db.heartRateDao.findById(id)!!
assertTrue(hr2.contentEqualsTo(hr3))
}
}

View file

@ -33,6 +33,7 @@ import info.nightscout.database.entities.EffectiveProfileSwitch
import info.nightscout.database.entities.ExtendedBolus
import info.nightscout.database.entities.Food
import info.nightscout.database.entities.GlucoseValue
import info.nightscout.database.entities.HeartRate
import info.nightscout.database.entities.MultiwaveBolusLink
import info.nightscout.database.entities.OfflineEvent
import info.nightscout.database.entities.PreferenceChange
@ -43,18 +44,20 @@ import info.nightscout.database.entities.TherapyEvent
import info.nightscout.database.entities.TotalDailyDose
import info.nightscout.database.entities.UserEntry
import info.nightscout.database.entities.VersionChange
import info.nightscout.database.impl.daos.HeartRateDao
import java.io.Closeable
const val DATABASE_VERSION = 23
const val DATABASE_VERSION = 24
@Database(version = DATABASE_VERSION,
entities = [APSResult::class, Bolus::class, BolusCalculatorResult::class, Carbs::class,
EffectiveProfileSwitch::class, ExtendedBolus::class, GlucoseValue::class, ProfileSwitch::class,
TemporaryBasal::class, TemporaryTarget::class, TherapyEvent::class, TotalDailyDose::class, APSResultLink::class,
MultiwaveBolusLink::class, PreferenceChange::class, VersionChange::class, UserEntry::class,
Food::class, DeviceStatus::class, OfflineEvent::class],
Food::class, DeviceStatus::class, OfflineEvent::class, HeartRate::class],
exportSchema = true)
@TypeConverters(Converters::class)
internal abstract class AppDatabase : RoomDatabase() {
internal abstract class AppDatabase : Closeable, RoomDatabase() {
abstract val glucoseValueDao: GlucoseValueDao
@ -96,4 +99,5 @@ internal abstract class AppDatabase : RoomDatabase() {
abstract val offlineEventDao: OfflineEventDao
}
abstract val heartRateDao: HeartRateDao
}

View file

@ -101,6 +101,7 @@ import kotlin.math.roundToInt
//database.foodDao.deleteOlderThan(than)
removed.add(Pair("DeviceStatus", database.deviceStatusDao.deleteOlderThan(than)))
removed.add(Pair("OfflineEvent", database.offlineEventDao.deleteOlderThan(than)))
removed.add(Pair("HeartRate", database.heartRateDao.deleteOlderThan(than)))
if (deleteTrackedChanges) {
removed.add(Pair("GlucoseValue", database.glucoseValueDao.deleteTrackedChanges()))
@ -119,6 +120,7 @@ import kotlin.math.roundToInt
removed.add(Pair("ApsResult", database.apsResultDao.deleteTrackedChanges()))
//database.foodDao.deleteHistory()
removed.add(Pair("OfflineEvent", database.offlineEventDao.deleteTrackedChanges()))
removed.add(Pair("HeartRate", database.heartRateDao.deleteTrackedChanges()))
}
val ret = StringBuilder()
removed
@ -143,8 +145,8 @@ import kotlin.math.roundToInt
.subscribeOn(Schedulers.io())
//BG READINGS -- including invalid/history records
fun findBgReadingByNSIdSingle(nsId: String): Single<ValueWrapper<GlucoseValue>> =
database.glucoseValueDao.findByNSIdMaybe(nsId).toWrappedSingle()
fun findBgReadingByNSId(nsId: String): GlucoseValue? =
database.glucoseValueDao.findByNSId(nsId)
fun getModifiedBgReadingsDataFromId(lastId: Long): Single<List<GlucoseValue>> =
database.glucoseValueDao.getModifiedFrom(lastId)
@ -186,6 +188,9 @@ import kotlin.math.roundToInt
.subscribeOn(Schedulers.io())
// TEMP TARGETS
fun findTemporaryTargetByNSId(nsId: String): TemporaryTarget? =
database.temporaryTargetDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
@ -253,6 +258,9 @@ import kotlin.math.roundToInt
// PROFILE SWITCH
fun findProfileSwitchByNSId(nsId: String): ProfileSwitch? =
database.profileSwitchDao.findByNSId(nsId)
fun getNextSyncElementProfileSwitch(id: Long): Maybe<Pair<ProfileSwitch, ProfileSwitch>> =
database.profileSwitchDao.getNextModifiedOrNewAfter(id)
.flatMap { nextIdElement ->
@ -309,6 +317,9 @@ import kotlin.math.roundToInt
database.profileSwitchDao.getLastId()
// EFFECTIVE PROFILE SWITCH
fun findEffectiveProfileSwitchByNSId(nsId: String): EffectiveProfileSwitch? =
database.effectiveProfileSwitchDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
@ -373,6 +384,9 @@ import kotlin.math.roundToInt
*
* It is a Maybe as there might be no next element.
* */
fun findTherapyEventByNSId(nsId: String): TherapyEvent? =
database.therapyEventDao.findByNSId(nsId)
fun getNextSyncElementTherapyEvent(id: Long): Maybe<Pair<TherapyEvent, TherapyEvent>> =
database.therapyEventDao.getNextModifiedOrNewAfter(id)
.flatMap { nextIdElement ->
@ -431,6 +445,9 @@ import kotlin.math.roundToInt
database.therapyEventDao.getLastId()
// FOOD
fun findFoodByNSId(nsId: String): Food? =
database.foodDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
@ -465,6 +482,9 @@ import kotlin.math.roundToInt
database.foodDao.getLastId()
// BOLUS
fun findBolusByNSId(nsId: String): Bolus? =
database.bolusDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
@ -531,6 +551,9 @@ import kotlin.math.roundToInt
database.bolusDao.getLastId()
// CARBS
fun findCarbsByNSId(nsId: String): Carbs? =
database.carbsDao.findByNSId(nsId)
private fun expandCarbs(carbs: Carbs): List<Carbs> =
if (carbs.duration == 0L) {
listOf(carbs)
@ -646,6 +669,9 @@ import kotlin.math.roundToInt
database.carbsDao.getLastId()
// BOLUS CALCULATOR RESULT
fun findBolusCalculatorResultByNSId(nsId: String): BolusCalculatorResult? =
database.bolusCalculatorResultDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
@ -709,13 +735,16 @@ import kotlin.math.roundToInt
database.deviceStatusDao.getLastId()
// TEMPORARY BASAL
fun findTemporaryBasalByNSId(nsId: String): TemporaryBasal? =
database.temporaryBasalDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
* of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully.
*
* It is a Maybe as there might be no next element.
* */
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
* of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully.
*
* It is a Maybe as there might be no next element.
* */
fun getNextSyncElementTemporaryBasal(id: Long): Maybe<Pair<TemporaryBasal, TemporaryBasal>> =
database.temporaryBasalDao.getNextModifiedOrNewAfter(id)
@ -773,13 +802,16 @@ import kotlin.math.roundToInt
database.temporaryBasalDao.getLastId()
// EXTENDED BOLUS
fun findExtendedBolusByNSId(nsId: String): ExtendedBolus? =
database.extendedBolusDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
* of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully.
*
* It is a Maybe as there might be no next element.
* */
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
* of the update ("historic") entry. The sync counter should be incremented to that id if it was synced successfully.
*
* It is a Maybe as there might be no next element.
* */
fun getNextSyncElementExtendedBolus(id: Long): Maybe<Pair<ExtendedBolus, ExtendedBolus>> =
database.extendedBolusDao.getNextModifiedOrNewAfter(id)
@ -844,6 +876,9 @@ import kotlin.math.roundToInt
}
// OFFLINE EVENT
fun findOfflineEventByNSId(nsId: String): OfflineEvent? =
database.offlineEventDao.findByNSId(nsId)
/*
* returns a Pair of the next entity to sync and the ID of the "update".
* The update id might either be the entry id itself if it is a new entry - or the id
@ -897,6 +932,11 @@ import kotlin.math.roundToInt
fun getLastOfflineEventId(): Long? =
database.offlineEventDao.getLastId()
fun getHeartRatesFromTime(timeMillis: Long) = database.heartRateDao.getFromTime(timeMillis)
fun getHeartRatesFromTimeToTime(startMillis: Long, endMillis: Long) =
database.heartRateDao.getFromTimeToTime(startMillis, endMillis)
suspend fun collectNewEntriesSince(since: Long, until: Long, limit: Int, offset: Int) = NewEntries(
apsResults = database.apsResultDao.getNewEntriesSince(since, until, limit, offset),
apsResultLinks = database.apsResultLinkDao.getNewEntriesSince(since, until, limit, offset),
@ -915,6 +955,7 @@ import kotlin.math.roundToInt
therapyEvents = database.therapyEventDao.getNewEntriesSince(since, until, limit, offset),
totalDailyDoses = database.totalDailyDoseDao.getNewEntriesSince(since, until, limit, offset),
versionChanges = database.versionChangeDao.getNewEntriesSince(since, until, limit, offset),
heartRates = database.heartRateDao.getNewEntriesSince(since, until, limit, offset),
)
}
@ -923,4 +964,3 @@ inline fun <reified T : Any> Maybe<T>.toWrappedSingle(): Single<ValueWrapper<T>>
this.map { ValueWrapper.Existing(it) as ValueWrapper<T> }
.switchIfEmpty(Maybe.just(ValueWrapper.Absent()))
.toSingle()

View file

@ -1,12 +1,14 @@
package info.nightscout.database.impl
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.room.Room
import androidx.room.RoomDatabase.Callback
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import dagger.Module
import dagger.Provides
import info.nightscout.database.entities.TABLE_HEART_RATE
import javax.inject.Qualifier
import javax.inject.Singleton
@ -22,13 +24,7 @@ open class DatabaseModule {
internal fun provideAppDatabase(context: Context, @DbFileName fileName: String) =
Room
.databaseBuilder(context, AppDatabase::class.java, fileName)
// .addMigrations(migration5to6)
// .addMigrations(migration6to7)
// .addMigrations(migration7to8)
// .addMigrations(migration11to12)
.addMigrations(migration20to21)
.addMigrations(migration21to22)
.addMigrations(migration22to23)
.addMigrations(*migrations)
.addCallback(object : Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
@ -89,4 +85,36 @@ open class DatabaseModule {
}
}
}
private val migration23to24 = object : Migration(23, 24) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""CREATE TABLE IF NOT EXISTS `$TABLE_HEART_RATE` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`duration` INTEGER NOT NULL,
`timestamp` INTEGER NOT NULL,
`beatsPerMinute` REAL NOT NULL,
`device` TEXT NOT NULL,
`utcOffset` INTEGER NOT NULL,
`version` INTEGER NOT NULL,
`dateCreated` INTEGER NOT NULL,
`isValid` INTEGER NOT NULL,
`referenceId` INTEGER,
`nightscoutSystemId` TEXT,
`nightscoutId` TEXT,
`pumpType` TEXT,
`pumpSerial` TEXT,
`temporaryId` INTEGER,
`pumpId` INTEGER, `startId` INTEGER,
`endId` INTEGER)""".trimIndent()
)
database.execSQL("""CREATE INDEX IF NOT EXISTS `index_heartRate_id` ON `$TABLE_HEART_RATE` (`id`)""")
database.execSQL("""CREATE INDEX IF NOT EXISTS `index_heartRate_timestamp` ON `$TABLE_HEART_RATE` (`timestamp`)""")
// Custom indexes must be dropped on migration to pass room schema checking after upgrade
dropCustomIndexes(database)
}
}
/** List of all migrations for easy reply in tests. */
@VisibleForTesting
internal val migrations = arrayOf(migration20to21, migration21to22, migration22to23, migration23to24)
}

View file

@ -41,6 +41,8 @@ import info.nightscout.database.impl.daos.delegated.DelegatedTotalDailyDoseDao
import info.nightscout.database.impl.daos.delegated.DelegatedUserEntryDao
import info.nightscout.database.impl.daos.delegated.DelegatedVersionChangeDao
import info.nightscout.database.entities.interfaces.DBEntry
import info.nightscout.database.impl.daos.HeartRateDao
import info.nightscout.database.impl.daos.delegated.DelegatedHeartRateDao
internal class DelegatedAppDatabase(val changes: MutableList<DBEntry>, val database: AppDatabase) {
@ -64,5 +66,6 @@ internal class DelegatedAppDatabase(val changes: MutableList<DBEntry>, val datab
val foodDao: FoodDao = DelegatedFoodDao(changes, database.foodDao)
val deviceStatusDao: DeviceStatusDao = DelegatedDeviceStatusDao(changes, database.deviceStatusDao)
val offlineEventDao: OfflineEventDao = DelegatedOfflineEventDao(changes, database.offlineEventDao)
val heartRateDao: HeartRateDao = DelegatedHeartRateDao(changes, database.heartRateDao)
fun clearAllTables() = database.clearAllTables()
}
}

View file

@ -29,7 +29,7 @@ internal interface GlucoseValueDao : TraceableDao<GlucoseValue> {
fun getLastId(): Long?
@Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE nightscoutId = :nsId AND referenceId IS NULL")
fun findByNSIdMaybe(nsId: String): Maybe<GlucoseValue>
fun findByNSId(nsId: String): GlucoseValue?
@Query("SELECT * FROM $TABLE_GLUCOSE_VALUES WHERE timestamp = :timestamp AND sourceSensor = :sourceSensor AND referenceId IS NULL")
fun findByTimestampAndSensor(timestamp: Long, sourceSensor: GlucoseValue.SourceSensor): GlucoseValue?

View file

@ -0,0 +1,31 @@
package info.nightscout.database.impl.daos
import androidx.room.Dao
import androidx.room.Query
import info.nightscout.database.entities.HeartRate
import info.nightscout.database.entities.TABLE_HEART_RATE
@Dao
internal interface HeartRateDao : TraceableDao<HeartRate> {
@Query("SELECT * FROM $TABLE_HEART_RATE WHERE id = :id")
override fun findById(id: Long): HeartRate?
@Query("DELETE FROM $TABLE_HEART_RATE")
override fun deleteAllEntries()
@Query("DELETE FROM $TABLE_HEART_RATE WHERE timestamp < :than")
override fun deleteOlderThan(than: Long): Int
@Query("DELETE FROM $TABLE_HEART_RATE WHERE referenceId IS NOT NULL")
override fun deleteTrackedChanges(): Int
@Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp >= :timestamp ORDER BY timestamp")
fun getFromTime(timestamp: Long): List<HeartRate>
@Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp BETWEEN :startMillis AND :endMillis ORDER BY timestamp")
fun getFromTimeToTime(startMillis: Long, endMillis: Long): List<HeartRate>
@Query("SELECT * FROM $TABLE_HEART_RATE WHERE timestamp > :since AND timestamp <= :until LIMIT :limit OFFSET :offset")
fun getNewEntriesSince(since: Long, until: Long, limit: Int, offset: Int): List<HeartRate>
}

View file

@ -0,0 +1,20 @@
package info.nightscout.database.impl.daos.delegated
import info.nightscout.database.entities.HeartRate
import info.nightscout.database.entities.interfaces.DBEntry
import info.nightscout.database.impl.daos.HeartRateDao
internal class DelegatedHeartRateDao(
changes: MutableList<DBEntry>,
private val dao:HeartRateDao): DelegatedDao(changes), HeartRateDao by dao {
override fun insertNewEntry(entry: HeartRate): Long {
changes.add(entry)
return dao.insertNewEntry(entry)
}
override fun updateExistingEntry(entry: HeartRate): Long {
changes.add(entry)
return dao.updateExistingEntry(entry)
}
}

View file

@ -0,0 +1,20 @@
package info.nightscout.database.impl.transactions
import info.nightscout.database.entities.HeartRate
class InsertOrUpdateHeartRateTransaction(private val heartRate: HeartRate):
Transaction<InsertOrUpdateHeartRateTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val existing = if (heartRate.id == 0L) null else database.heartRateDao.findById(heartRate.id)
return if (existing == null) {
database.heartRateDao.insertNewEntry(heartRate).let {
TransactionResult(listOf(heartRate), emptyList()) }
} else {
database.heartRateDao.updateExistingEntry(heartRate)
TransactionResult(emptyList(), listOf(heartRate))
}
}
data class TransactionResult(val inserted: List<HeartRate>, val updated: List<HeartRate>)
}

View file

@ -8,10 +8,11 @@ class InvalidateBolusCalculatorResultTransaction(val id: Long) : Transaction<Inv
val result = TransactionResult()
val bolusCalculatorResult = database.bolusCalculatorResultDao.findById(id)
?: throw IllegalArgumentException("There is no such BolusCalculatorResult with the specified ID.")
bolusCalculatorResult.isValid = false
database.bolusCalculatorResultDao.updateExistingEntry(bolusCalculatorResult)
result.invalidated.add(bolusCalculatorResult)
if (bolusCalculatorResult.isValid) {
bolusCalculatorResult.isValid = false
database.bolusCalculatorResultDao.updateExistingEntry(bolusCalculatorResult)
result.invalidated.add(bolusCalculatorResult)
}
return result
}

View file

@ -8,9 +8,11 @@ class InvalidateBolusTransaction(val id: Long) : Transaction<InvalidateBolusTran
val result = TransactionResult()
val bolus = database.bolusDao.findById(id)
?: throw IllegalArgumentException("There is no such Bolus with the specified ID.")
bolus.isValid = false
database.bolusDao.updateExistingEntry(bolus)
result.invalidated.add(bolus)
if (bolus.isValid) {
bolus.isValid = false
database.bolusDao.updateExistingEntry(bolus)
result.invalidated.add(bolus)
}
return result
}

View file

@ -8,9 +8,11 @@ class InvalidateCarbsTransaction(val id: Long) : Transaction<InvalidateCarbsTran
val result = TransactionResult()
val carbs = database.carbsDao.findById(id)
?: throw IllegalArgumentException("There is no such Carbs with the specified ID.")
carbs.isValid = false
database.carbsDao.updateExistingEntry(carbs)
result.invalidated.add(carbs)
if (carbs.isValid) {
carbs.isValid = false
database.carbsDao.updateExistingEntry(carbs)
result.invalidated.add(carbs)
}
return result
}

View file

@ -0,0 +1,23 @@
package info.nightscout.database.impl.transactions
import info.nightscout.database.entities.EffectiveProfileSwitch
class InvalidateEffectiveProfileSwitchTransaction(val id: Long) : Transaction<InvalidateEffectiveProfileSwitchTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val result = TransactionResult()
val effectiveProfileSwitch = database.effectiveProfileSwitchDao.findById(id)
?: throw IllegalArgumentException("There is no such EffectiveProfileSwitch with the specified ID.")
if (effectiveProfileSwitch.isValid) {
effectiveProfileSwitch.isValid = false
database.effectiveProfileSwitchDao.updateExistingEntry(effectiveProfileSwitch)
result.invalidated.add(effectiveProfileSwitch)
}
return result
}
class TransactionResult {
val invalidated = mutableListOf<EffectiveProfileSwitch>()
}
}

View file

@ -8,9 +8,11 @@ class InvalidateExtendedBolusTransaction(val id: Long) : Transaction<InvalidateE
val result = TransactionResult()
val extendedBolus = database.extendedBolusDao.findById(id)
?: throw IllegalArgumentException("There is no such Extended Bolus with the specified ID.")
extendedBolus.isValid = false
database.extendedBolusDao.updateExistingEntry(extendedBolus)
result.invalidated.add(extendedBolus)
if (extendedBolus.isValid) {
extendedBolus.isValid = false
database.extendedBolusDao.updateExistingEntry(extendedBolus)
result.invalidated.add(extendedBolus)
}
return result
}

View file

@ -5,7 +5,9 @@ class InvalidateFoodTransaction(val id: Long) : Transaction<Unit>() {
override fun run() {
val food = database.foodDao.findById(id)
?: throw IllegalArgumentException("There is no such Food with the specified ID.")
food.isValid = false
database.foodDao.updateExistingEntry(food)
if (food.isValid) {
food.isValid = false
database.foodDao.updateExistingEntry(food)
}
}
}

View file

@ -11,9 +11,11 @@ class InvalidateGlucoseValueTransaction(val id: Long) : Transaction<InvalidateGl
val result = TransactionResult()
val glucoseValue = database.glucoseValueDao.findById(id)
?: throw IllegalArgumentException("There is no such GlucoseValue with the specified ID.")
glucoseValue.isValid = false
database.glucoseValueDao.updateExistingEntry(glucoseValue)
result.invalidated.add(glucoseValue)
if (glucoseValue.isValid) {
glucoseValue.isValid = false
database.glucoseValueDao.updateExistingEntry(glucoseValue)
result.invalidated.add(glucoseValue)
}
return result
}

View file

@ -1,22 +0,0 @@
package info.nightscout.database.impl.transactions
import info.nightscout.database.entities.ProfileSwitch
class InvalidateNsIdProfileSwitchTransaction(val nsId: String) : Transaction<InvalidateNsIdProfileSwitchTransaction.TransactionResult>() {
override fun run() : TransactionResult{
val result = TransactionResult()
val current = database.profileSwitchDao.findByNSId(nsId)
if (current != null) {
current.isValid = false
database.profileSwitchDao.updateExistingEntry(current)
result.invalidated.add(current)
}
return result
}
class TransactionResult {
val invalidated = mutableListOf<ProfileSwitch>()
}
}

View file

@ -1,10 +1,23 @@
package info.nightscout.database.impl.transactions
class InvalidateOfflineEventTransaction(val id: Long) : Transaction<Unit>() {
override fun run() {
import info.nightscout.database.entities.OfflineEvent
class InvalidateOfflineEventTransaction(val id: Long) : Transaction<InvalidateOfflineEventTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val result = TransactionResult()
val offlineEvent = database.offlineEventDao.findById(id)
?: throw IllegalArgumentException("There is no such OfflineEvent with the specified ID.")
offlineEvent.isValid = false
database.offlineEventDao.updateExistingEntry(offlineEvent)
if (offlineEvent.isValid) {
offlineEvent.isValid = false
database.offlineEventDao.updateExistingEntry(offlineEvent)
result.invalidated.add(offlineEvent)
}
return result
}
class TransactionResult {
val invalidated = mutableListOf<OfflineEvent>()
}
}

View file

@ -8,9 +8,11 @@ class InvalidateProfileSwitchTransaction(val id: Long) : Transaction<InvalidateP
val result = TransactionResult()
val profileSwitch = database.profileSwitchDao.findById(id)
?: throw IllegalArgumentException("There is no such ProfileSwitch with the specified ID.")
profileSwitch.isValid = false
database.profileSwitchDao.updateExistingEntry(profileSwitch)
result.invalidated.add(profileSwitch)
if (profileSwitch.isValid) {
profileSwitch.isValid = false
database.profileSwitchDao.updateExistingEntry(profileSwitch)
result.invalidated.add(profileSwitch)
}
return result
}

View file

@ -8,9 +8,11 @@ class InvalidateTemporaryBasalTransaction(val id: Long) : Transaction<Invalidate
val result = TransactionResult()
val temporaryBasal = database.temporaryBasalDao.findById(id)
?: throw IllegalArgumentException("There is no such Temporary Basal with the specified ID.")
temporaryBasal.isValid = false
database.temporaryBasalDao.updateExistingEntry(temporaryBasal)
result.invalidated.add(temporaryBasal)
if (temporaryBasal.isValid) {
temporaryBasal.isValid = false
database.temporaryBasalDao.updateExistingEntry(temporaryBasal)
result.invalidated.add(temporaryBasal)
}
return result
}

View file

@ -10,9 +10,11 @@ class InvalidateTemporaryBasalTransactionWithPumpId(val pumpId: Long, val pumpTy
val result = TransactionResult()
val temporaryBasal = database.temporaryBasalDao.findByPumpIds(pumpId, pumpType, pumpSerial)
?: throw IllegalArgumentException("There is no such Temporary Basal with the specified temp ID.")
temporaryBasal.isValid = false
database.temporaryBasalDao.updateExistingEntry(temporaryBasal)
result.invalidated.add(temporaryBasal)
if (temporaryBasal.isValid) {
temporaryBasal.isValid = false
database.temporaryBasalDao.updateExistingEntry(temporaryBasal)
result.invalidated.add(temporaryBasal)
}
return result
}

View file

@ -8,9 +8,11 @@ class InvalidateTemporaryBasalWithTempIdTransaction(val tempId: Long) : Transact
val result = TransactionResult()
val temporaryBasal = database.temporaryBasalDao.findByTempId(tempId)
?: throw IllegalArgumentException("There is no such Temporary Basal with the specified temp ID.")
temporaryBasal.isValid = false
database.temporaryBasalDao.updateExistingEntry(temporaryBasal)
result.invalidated.add(temporaryBasal)
if (temporaryBasal.isValid) {
temporaryBasal.isValid = false
database.temporaryBasalDao.updateExistingEntry(temporaryBasal)
result.invalidated.add(temporaryBasal)
}
return result
}

View file

@ -1,10 +1,23 @@
package info.nightscout.database.impl.transactions
class InvalidateTemporaryTargetTransaction(val id: Long) : Transaction<Unit>() {
override fun run() {
import info.nightscout.database.entities.TemporaryTarget
class InvalidateTemporaryTargetTransaction(val id: Long) : Transaction<InvalidateTemporaryTargetTransaction.TransactionResult>() {
override fun run(): TransactionResult {
val result = TransactionResult()
val temporaryTarget = database.temporaryTargetDao.findById(id)
?: throw IllegalArgumentException("There is no such TemporaryTarget with the specified ID.")
temporaryTarget.isValid = false
database.temporaryTargetDao.updateExistingEntry(temporaryTarget)
if (temporaryTarget.isValid) {
temporaryTarget.isValid = false
database.temporaryTargetDao.updateExistingEntry(temporaryTarget)
result.invalidated.add(temporaryTarget)
}
return result
}
class TransactionResult {
val invalidated = mutableListOf<TemporaryTarget>()
}
}

View file

@ -8,9 +8,11 @@ class InvalidateTherapyEventTransaction(val id: Long) : Transaction<InvalidateTh
val result = TransactionResult()
val therapyEvent = database.therapyEventDao.findById(id)
?: throw IllegalArgumentException("There is no such TherapyEvent with the specified ID.")
therapyEvent.isValid = false
database.therapyEventDao.updateExistingEntry(therapyEvent)
result.invalidated.add(therapyEvent)
if (therapyEvent.isValid) {
therapyEvent.isValid = false
database.therapyEventDao.updateExistingEntry(therapyEvent)
result.invalidated.add(therapyEvent)
}
return result
}

View file

@ -24,3 +24,11 @@ android.useAndroidX=true
android.nonTransitiveRClass=true
# Cache is causeing issues with CircleCI nad maybe Studio 2021
# org.gradle.unsafe.configuration-cache=true
# After migration to kotlin 1.8.21
#e: org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during psi2ir
# File being compiled: (37,18) in /home/circleci/project/core/interfaces/src/main/java/info/nightscout/interfaces/pump/defs/PumpCapability.kt
# The root cause org.jetbrains.kotlin.psi2ir.generators.ErrorExpressionException was thrown at: org.jetbrains.kotlin.psi2ir.generators.ErrorExpressionGenerator.generateErrorCall(ErrorExpressionGenerator.kt:100)
# null: KtCallExpression
# https://youtrack.jetbrains.com/issue/KT-58027
kapt.use.jvm.ir=false

View file

@ -29,16 +29,14 @@ class UserEntryLoggerImpl @Inject constructor(
private val compositeDisposable = CompositeDisposable()
override fun log(action: Action, source: Sources, note: String?, vararg listValues: ValueWithUnit?) = log(action, source, note, listValues.toList())
override fun log(action: Action, source: Sources, note: String?, timestamp: Long, vararg listValues: ValueWithUnit?) = log(action, source, note, timestamp, listValues.toList())
override fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?) = log(action, source, "", listValues.toList())
override fun log(action: Action, source: Sources, note: String?, listValues: List<ValueWithUnit?>) {
override fun log(action: Action, source: Sources, note: String?, timestamp: Long, listValues: List<ValueWithUnit?>) {
val filteredValues = listValues.toList().filterNotNull()
log(
listOf(
UserEntry(
timestamp = dateUtil.now(),
timestamp = timestamp,
action = action,
source = source,
note = note ?: "",
@ -48,6 +46,12 @@ class UserEntryLoggerImpl @Inject constructor(
)
}
override fun log(action: Action, source: Sources, note: String?, vararg listValues: ValueWithUnit?) = log(action, source, note, listValues.toList())
override fun log(action: Action, source: Sources, vararg listValues: ValueWithUnit?) = log(action, source, "", listValues.toList())
override fun log(action: Action, source: Sources, note: String?, listValues: List<ValueWithUnit?>) = log(action, source, note, dateUtil.now(), listValues)
override fun log(entries: List<UserEntry>) {
compositeDisposable += repository.runTransactionForResult(UserEntryTransaction(entries))
.subscribeOn(aapsSchedulers.io)

View file

@ -87,6 +87,7 @@ class OverviewDataImpl @Inject constructor(
dsMinSeries = LineGraphSeries()
treatmentsSeries = PointsWithLabelGraphSeries()
epsSeries = PointsWithLabelGraphSeries()
heartRateGraphSeries = LineGraphSeries()
}
override fun initRange() {
@ -322,4 +323,6 @@ class OverviewDataImpl @Inject constructor(
override val dsMinScale = Scale()
override var dsMaxSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
override var dsMinSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
override var heartRateScale = Scale()
override var heartRateGraphSeries: LineGraphSeries<DataPointWithLabelInterface> = LineGraphSeries()
}

View file

@ -71,7 +71,7 @@ class ProfileFunctionImpl @Inject constructor(
override fun getProfileNameWithRemainingTime(): String =
getProfileName(System.currentTimeMillis(), customized = true, showRemainingTime = true)
fun getProfileName(time: Long, customized: Boolean, showRemainingTime: Boolean): String {
private fun getProfileName(time: Long, customized: Boolean, showRemainingTime: Boolean): String {
var profileName = rh.gs(info.nightscout.core.ui.R.string.no_profile_set)
val profileSwitch = repository.getEffectiveProfileSwitchActiveAt(time).blockingGet()

View file

@ -273,7 +273,13 @@ class PumpSyncImplementation @Inject constructor(
pumpSerial = pumpSerial
)
)
uel.log(UserEntry.Action.CAREPORTAL, pumpType.source.toDbSource(), note, ValueWithUnit.Timestamp(timestamp), ValueWithUnit.TherapyEventType(type.toDBbEventType()))
uel.log(
action = UserEntry.Action.CAREPORTAL,
source = pumpType.source.toDbSource(),
note = note,
timestamp = timestamp,
ValueWithUnit.Timestamp(timestamp), ValueWithUnit.TherapyEventType(type.toDBbEventType())
)
repository.runTransactionForResult(InsertIfNewByTimestampTherapyEventTransaction(therapyEvent))
.doOnError {
aapsLogger.error(LTag.DATABASE, "Error while saving TherapyEvent", it)

View file

@ -113,12 +113,13 @@ class CommandQueueImplementation @Inject constructor(
return@subscribe
}
aapsLogger.debug(LTag.PROFILE, "onEventProfileSwitchChanged")
val effective = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
profileFunction.getRequestedProfile()?.let {
setProfile(ProfileSealed.PS(it), it.interfaceIDs.nightscoutId != null, object : Callback() {
override fun run() {
if (!result.success) {
uiInteraction.runAlarm(result.comment, rh.gs(info.nightscout.core.ui.R.string.failed_update_basal_profile), info.nightscout.core.ui.R.raw.boluserror)
} else if (result.enacted) {
} else if (result.enacted || effective is ValueWrapper.Existing && effective.value.originalEnd < dateUtil.now() && effective.value.originalDuration != 0L) {
val nonCustomized = ProfileSealed.PS(it).convertToNonCustomizedProfile(dateUtil)
EffectiveProfileSwitch(
timestamp = dateUtil.now(),
@ -421,7 +422,7 @@ class CommandQueueImplementation @Inject constructor(
}
// returns true if command is queued
override fun setProfile(profile: Profile, hasNsId: Boolean, callback: Callback?): Boolean {
fun setProfile(profile: ProfileSealed, hasNsId: Boolean, callback: Callback?): Boolean {
if (isRunning(CommandType.BASAL_PROFILE)) {
aapsLogger.debug(LTag.PUMPQUEUE, "Command is already executed")
callback?.result(PumpEnactResult(injector).success(true).enacted(false))?.run()

View file

@ -19,6 +19,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat
import info.nightscout.core.ui.getThemeColor
import info.nightscout.core.ui.locale.LocaleHelper
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.shared.interfaces.ResourceHelper
import java.util.Locale
@ -29,15 +30,12 @@ import javax.inject.Inject
*/
class ResourceHelperImpl @Inject constructor(var context: Context, private val fabricPrivacy: FabricPrivacy) : ResourceHelper {
override fun updateContext(ctx: Context?) {
ctx?.let { context = it }
}
override fun gs(@StringRes id: Int): String = context.getString(id)
override fun gs(@StringRes id: Int): String =
context.createConfigurationContext(Configuration().apply { setLocale(LocaleHelper.currentLocale(context)) }).resources.getString(id)
override fun gs(@StringRes id: Int, vararg args: Any?): String {
return try {
context.getString(id, *args)
context.createConfigurationContext(Configuration().apply { setLocale(LocaleHelper.currentLocale(context)) }).resources.getString(id, *args)
} catch (exception: Exception) {
val resourceName = context.resources.getResourceEntryName(id)
val resourceValue = context.getString(id)

View file

@ -3,21 +3,21 @@
<string name="alert_r7_description"><![CDATA[Kiekis: <b>%1$d%%</b>\nTrukmė: <b>%2$s h</b>]]></string>
<string name="alert_w31_description"><![CDATA[Rezervuaro tūris: <b>%1$s U</b>]]></string>
<string name="alert_w32_description">Pakeiskite bateriją.</string>
<string name="alert_w33_description">Nustatykite laiką/datą.</string>
<string name="alert_w33_description">Nustatyti laiką/datą.</string>
<string name="alert_w34_description">Kreipkitės į Accu-Chek palaikymo tarnybą.</string>
<string name="alert_w36_description"><![CDATA[Kiekis: <b>%1$d%%</b><br/>Trukmė: <b>%2$s h</b>]]></string>
<string name="alert_w38_description"><![CDATA[Suprogramuota: <b>%1$s U</b><br/>Suleista: <b>%2$s U</b>]]></string>
<string name="alert_m20_description">Įdėkite rezervuarą.</string>
<string name="alert_m21_description">Pakeiskite rezervuarą.</string>
<string name="alert_m22_description">Pakeiskite bateriją.</string>
<string name="alert_m23_description">Patikrinkite pompos būseną.</string>
<string name="alert_m23_description">Patikrinkite pompos statusą.</string>
<string name="alert_m24_description">Pakeiskite infuzijos rinkinį.</string>
<string name="alert_m25_description">Kreipkitės į Accu-Chek palaikymo tarnybą.</string>
<string name="alert_m26_description">Pakeiskite rezervuarą.</string>
<string name="alert_m27_description">Iš naujo paleiskite duomenų atsisiuntimą.</string>
<string name="alert_m28_description">Patikrinkite pompos būseną.</string>
<string name="alert_m29_description">Nustatykite baterijos tipą.</string>
<string name="alert_m30_description">Nustatykite rezervuaro tipą.</string>
<string name="alert_m28_description">Patikrinkite pompos statusą.</string>
<string name="alert_m29_description">Nustatyti baterijos tipą.</string>
<string name="alert_m30_description">Nustatyti rezervuaro tipą.</string>
<string name="alert_e6_description">Pakeiskite bateriją ir rezervuarą.</string>
<string name="alert_e10_description">Pakeiskite rezervuarą.</string>
<string name="alert_e13_description">Pakeiskite kalbą.</string>

View file

@ -4,7 +4,7 @@
<string name="disable_tbr_over_notification">Išjungti pranešimus apie LBD pabaigą\n(pompos nustatymai)</string>
<string name="not_paired">Nesusieta</string>
<string name="recovering">Atstatoma</string>
<string name="insight_status">Būsena</string>
<string name="insight_status">Statusas</string>
<string name="recovery_duration">Atkūrimo trukmė</string>
<string name="last_connected">Paskutinis prisijungimas</string>
<string name="start_pump">Paleisti pompą</string>
@ -21,9 +21,9 @@
<string name="eb_formatter">%1$.2f / %2$.2f vv per %3$d min</string>
<string name="insight_last_bolus">Paskutinis bolusas</string>
<string name="searching_for_devices">Ieškoma įrenginių…</string>
<string name="pairing_completed">Sujungimas sėkmingas</string>
<string name="pairing_completed">Sėkmingai susieta</string>
<string name="code_compare">Ar kodas, kurį matote įrenginyje, sutampa su pompos kodu?</string>
<string name="insight_pairing">Insight sujungimas</string>
<string name="insight_pairing">Insight susiejimas</string>
<string name="insight_local">Accu-Chek Insight</string>
<string name="insight_alert_formatter">%1$s: %2$s</string>
<string name="tube_changed">Kateteris pakeistas</string>
@ -36,7 +36,7 @@
<string name="timeout_during_handshake">Ryšio užmezgimui skirtas laikas baigėsi - iš naujo nustatykite bluetooth</string>
<string name="pump_stopped">Pompa sustabdyta</string>
<string name="pump_started">Pompa paleista</string>
<string name="short_status_last_connected">Paskutinis prisijungimas: prieš %1$d min</string>
<string name="short_status_last_connected">Pask. prijung: prieš %1$d min</string>
<string name="short_status_tbr">LBD: %1$d%% - %2$d / %3$d min</string>
<string name="short_status_extended">Ištęstas: %1$.2f / %2$.2f V %3$d min</string>
<string name="short_status_multiwave">Daugiabangis: %1$.2f / %2$.2f vv %3$d min</string>
@ -52,7 +52,7 @@
<string name="bluetooth_address">Bluetooth adresas</string>
<string name="system_id_appendix">Sistemos ID priedėlis</string>
<string name="manufacturing_date">Pagaminimo data</string>
<string name="delete_pairing">Panaikinti sąsają</string>
<string name="delete_pairing">Panaikinti susiejimą</string>
<string name="log_site_changes">Įrašyti adatos pakeitimą</string>
<string name="log_reservoir_changes">Įrašyti rezervuaro keitimus</string>
<string name="log_tube_changes">Įrašyti infuzijos vamzdelio pakeitimą</string>
@ -65,6 +65,6 @@
<string name="max_recovery_duration">Didž. atkūrimo trukmė [s]</string>
<string name="min_recovery_duration">Min. atkūrimo trukmė [s]</string>
<string name="pump_alert">Pompos aliarmas</string>
<string name="pairing_information">Sąsajos informacija</string>
<string name="insight_refresh_button" comment="26 characters max for translation">Insight Mygtukas Naujinti</string>
<string name="pairing_information">Susiejimo informacija</string>
<string name="insight_refresh_button" comment="26 characters max for translation">Naujinti Insight mygtukas </string>
</resources>

View file

@ -10,6 +10,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuCompat
import androidx.core.view.MenuProvider
import androidx.lifecycle.Lifecycle
import dagger.android.support.DaggerFragment
@ -72,7 +73,7 @@ class OpenAPSFragment : DaggerFragment(), MenuProvider {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.openapsma_run)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
MenuCompat.setGroupDividerEnabled(menu, true)
}
override fun onMenuItemSelected(item: MenuItem): Boolean =

View file

@ -9,6 +9,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuCompat
import androidx.core.view.MenuProvider
import androidx.lifecycle.Lifecycle
import dagger.android.support.DaggerFragment
@ -74,7 +75,7 @@ class LoopFragment : DaggerFragment(), MenuProvider {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.run_now)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
MenuCompat.setGroupDividerEnabled(menu, true)
}
override fun onMenuItemSelected(item: MenuItem): Boolean =
@ -169,4 +170,4 @@ class LoopFragment : DaggerFragment(), MenuProvider {
binding.smbsetbypump.text = ""
binding.swipeRefresh.isRefreshing = false
}
}
}

View file

@ -25,7 +25,7 @@
<string name="description_ama">2017 m. algoritmas</string>
<string name="description_smb">Naujausias algoritmas patyrusiems vartotojams</string>
<string name="description_smb_dynamic_isf">Naujausias algoritmas patyrusiems naudotojams su dinaminiu/automatiniu JIF</string>
<string name="openapsama_bolus_snooze_dia_divisor">Boluso snaudimo daliklis</string>
<string name="openapsama_bolus_snooze_dia_divisor">Atidėjimo po boluso IVT daliklis</string>
<string name="openapsma_run">Paleisti dabar</string>
<string name="openapsma_last_run_label">Paskutinis veiksmas</string>
<string name="openapsma_input_parameters_label">Įvesties parametrai</string>
@ -48,7 +48,7 @@
<string name="openapsama_min_5m_carb_impact_summary">Numatytoji reikšmė: 3.0 (AMA) arba 8.0 (SMB). Tai parametras, nurodantis angliavandenių poveikį glikemijai kas 5 minutes nuo jų suvartojimo. Numatytoji reikšmė yra 3 mg/dl per 5min. Šis skaičius turi įtakos apskaičiavimams, kaip greitai mažės AAO, kokia bus glikemijos kitimo prognozė, ypač kai ji krenta daugiau nei tikėtasi, arba nedidėja tiek, kiek tikėtasi.</string>
<string name="openapsama_max_daily_safety_multiplier_summary">Numatytoji reikšmė: 3 tai pagrindinis OpenAPS saugiklis. Jis apriboja Jūsų valandinę bazę iki trigubos maksimalios valandinės bazės (standartiniu atveju). Jums greičiausiai neprireiks šios reikšmės keisti, tačiau turėtumėte žinoti, kad ji naudojama kaip saugiklis apskaičiuojant \"3x maksimali dienos bazė; 4x dabartinė valandinė bazė\".</string>
<string name="openapsama_current_basal_safety_multiplier_summary">Numatytoji reikšmė: 4 tai antras pagrindinis OpenAPS saugiklis, apskaičiuojant \"3x maksimali dienos bazė; 4x dabartinė valandinė bazė\". Jis reiškia, kad jūsų valandinė bazė, nepriklausomai nuo to, kokia maksimali valandinė bazė suprogramuota pompoje, negali būti didesnė, nei keturguba dabartinė valandinė bazė. Tai apsaugo Jus nuo pavojingų situacijų, kai nustatoma pernelyg didelė valandinė bazė, pilnai nesuprantant, kaip veikia algoritmas. Numatytoji reikšmė yra 4x; daugumai vartotojų niekada neprireikia šio skaičiaus keisti, o pajutus, kad \"atsitrenkiama\" į saugiklį, rekomenduojama peržiūrėti kitus nustatymus.</string>
<string name="openapsama_bolus_snooze_dia_divisor_summary">Numatytoji reikšmė: 2\nBoluso snaudimas aktyvuojamas iškart po to, kai susileidžiate bolusą maistui. Ši funkcija neleidžia sistemai nustatyti mažų LBD iškart po valgio. Pvz.: jei IVT yra 3 val, tai boluso snaudimas pamažu deaktyvuojamas per 1,5 val (3 val. / 2).</string>
<string name="openapsama_bolus_snooze_dia_divisor_summary">Numatytoji reikšmė: 2\nAtidėjimas po boluso aktyvuojamas iškart po to, kai susileidžiate bolusą maistui. Ši funkcija neleidžia sistemai nustatyti mažų laikinų bazių iškart po valgio. Pvz.: jei IVT yra 3 val, tai atidėjimas po boluso pamažu deaktyvuojamas per 1,5 val (3 val. / 2).</string>
<string name="openapsama_link_to_preference_json_doc_txt">Dėmesio!\nPaprastai neturėtumėte keisti šių, žemiau esančių, reikšmių. Prašome PASPAUSTI ČIA ir PERSKAITYKITE tekstą ir įsitikinkite, kad SUPRANTATE prieš keisdami bet kurią iš šių verčių.</string>
<string name="always_use_short_avg">Visada naudoti trumpo laikotarpio vidutinį pokyti vietoj paprasto pokyčio</string>
<string name="always_use_short_avg_summary">Naudinga, kai duomenys, gaunami iš nefiltruoto šaltinio, tokio kaip xDrip+, tampa nestabilūs.</string>

View file

@ -16,6 +16,7 @@ import android.widget.ImageView
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.core.util.forEach
import androidx.core.view.MenuCompat
import androidx.core.view.MenuProvider
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.ItemTouchHelper
@ -61,7 +62,6 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider {
const val ID_MENU_ADD = 504
const val ID_MENU_RUN = 505
const val ID_MENU_EDIT_MOVE = 506
}
private var disposable: CompositeDisposable = CompositeDisposable()
@ -98,8 +98,7 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider {
actionHelper.onCreateOptionsMenu(menu, inflater)
menu.add(Menu.FIRST, ID_MENU_ADD, 0, rh.gs(R.string.add_automation)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.add(Menu.FIRST, ID_MENU_RUN, 0, rh.gs(R.string.run_automations)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.add(Menu.FIRST, ID_MENU_EDIT_MOVE, 0, rh.gs(R.string.remove_sort)).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
menu.setGroupDividerEnabled(true)
MenuCompat.setGroupDividerEnabled(menu, true)
}
override fun onMenuItemSelected(item: MenuItem): Boolean =
@ -115,11 +114,6 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider {
true
}
ID_MENU_EDIT_MOVE -> {
actionHelper.startAction()
true
}
else -> super.onContextItemSelected(item)
}
@ -316,4 +310,4 @@ class AutomationFragment : DaggerFragment(), OnStartDragListener, MenuProvider {
}
}.show(childFragmentManager, "EditEventDialog")
}
}
}

View file

@ -116,7 +116,6 @@
<string name="system_automation">Системна автомация</string>
<string name="run_automations">Стартирай автомаций</string>
<string name="add_automation">Добавяне на правило</string>
<string name="remove_sort">Премахни/сортирай</string>
<string name="stop_processing">Спри изпълнението</string>
<!-- WeekdayPicker -->
<string name="monday_short">П</string>

View file

@ -116,7 +116,6 @@
<string name="system_automation">Automatizace systému</string>
<string name="run_automations">Spustit automatizace</string>
<string name="add_automation">Přidat pravidlo</string>
<string name="remove_sort">Odstranit/řadit</string>
<string name="stop_processing">Zastavit zpracovávání</string>
<!-- WeekdayPicker -->
<string name="monday_short">Po</string>

View file

@ -116,7 +116,6 @@
<string name="system_automation">System automatisering</string>
<string name="run_automations">Kør automatisering</string>
<string name="add_automation">Tilføj regel</string>
<string name="remove_sort">Fjern/sorter</string>
<string name="stop_processing">Stop afvikling</string>
<!-- WeekdayPicker -->
<string name="monday_short">Ma</string>

View file

@ -116,7 +116,6 @@
<string name="system_automation">System-Automatisierung</string>
<string name="run_automations">Automatisierungen ausführen</string>
<string name="add_automation">Regel hinzufügen</string>
<string name="remove_sort">Entfernen/sortieren</string>
<string name="stop_processing">Verarbeitung beenden</string>
<!-- WeekdayPicker -->
<string name="monday_short">Mo</string>

Some files were not shown because too many files have changed in this diff Show more